Avalanche AI Architecture
Avalanche AI is one shared agent runtime hosting seven Avalanche intelligence domains on top of the DeepSeek Harness. This document is the ordered map of that runtime: what each layer owns, how a task flows through it, and where each decision is enforced. It assumes the upstream composition model described in UPSTREAM_AUDIT.md.
Shape of the composition
dsh --profile avalanche
└── profile layers
├── @deepseek-ai/dsh-base upstream: LLM, sessions, tools, prompts, approvals
└── @deepseek-ai/dsh-avalanche-app fork: cordis.patch.yml (one insert block)
├── avalanche-context layered execution context (project → execution)
├── avalanche-memory layered memory (working → long-term)
├── avalanche-policy default-deny capability policy + approvals
├── avalanche-verification named verifier registry
├── avalanche-evidence hash-chained ledger + receipts
├── avalanche-tooling tool catalog + executor-level policy gate
├── avalanche-network network registry + read-only RPC tools + chain-id verifier
├── avalanche-agents domain descriptor registry + deterministic routing
├── avalanche-domain-developer … avalanche-domain-defi (7 descriptor plugins)
├── avalanche-orchestrator intent → policy → plan → tools → verify → receipt
├── avalanche-cli/startup commander parser; publishes the accepted invocation
└── avalanche-cli runner; injects runtime services and executes it
Nothing in the list is a separate process or a separate runtime. Every domain is a data descriptor registered into the same avalancheDomains service; every tool is registered on the same ctx.tools; every policy check crosses the same avalanchePolicy; every record lands in the same avalancheEvidence ledger.
Services
| Service | Package | Responsibility |
|---|---|---|
avalancheContext | avalanche-context | Six-layer context store (project, repository, network, user, task, execution) with precedence resolution and origin on every write |
avalancheMemory | avalanche-memory | Five-tier memory (working, episodic, semantic, procedural, long-term) with deterministic lexical recall and optional durable store |
avalanchePolicy | avalanche-policy | Capability-based, default-deny rule evaluation; approval request/decide/consume bound to a request fingerprint |
avalancheVerification | avalanche-verification | Named verifiers; unknown kind is unverifiable, never verified |
avalancheEvidence | avalanche-evidence | Append-only hash-chained ledger; subscribes to tools/result; issues tamper-evident receipts; optional JSONL mirror |
avalancheTooling | avalanche-tooling | Risk-annotated tool catalog; registers tools on ctx.tools; tools/pre-execute gate; fails closed when policy is absent |
avalancheNetworks | avalanche-network | Network descriptors (fuji 43113, mainnet 43114, local 43112); read-only JSON-RPC allowlist; five read tools; chain-id verifier |
avalancheDomains | avalanche-agents | Domain descriptor registry; keyword scoring and deterministic match()/best(); per-agent prompt and tool mask installation |
avalancheOrchestrator | avalanche-orchestrator | Task lifecycle, durable task records, agent dispatch, verification, receipt issuance |
Execution lifecycle
The runtime implements the fixed lifecycle the Avalanche AI brief requires:
INTENT → POLICY CHECK → EXECUTION PLAN → TOOL EXECUTION → SIMULATION → APPROVAL → EXECUTION → VERIFICATION → EVIDENCE RECEIPT
What each stage means in this build, and where it happens:
- INTENT.
avalancheOrchestrator.submit()turns the objective into anIntentSpec: id, objective, action, capability set derived from the owning domain descriptor, constraints, expected outcomes, risk, and optional network. The intent is recorded in the ledger before any decision. - POLICY CHECK.
avalanchePolicy.evaluate()unions the rules matching the requested capability classes and takes the strictest verdict (deny>require-approval>allow). Baseline rules allowchain:read,code:generate, andchain:simulate; parklocal:write,local:execute,tx:prepare,tx:sign,chain:write,infra:operatebehind approval; denytx:sign,chain:write, andinfra:operateonmainnet; deny any uncovered capability (default-deny); and deny any request whose declared risk is below the risk of its capabilities (risk-understatement). - EXECUTION PLAN. The orchestrator selects up to
maxStepsdomains throughavalancheDomains.match()(or one forced domain) and creates one plan step per domain. Routing is lexical and deterministic: the same objective always produces the same plan, and the matched keywords are inspectable. - TOOL EXECUTION. The orchestrator creates an agent through
ctx.agents, installs the domain prompt section and tool mask in the agent scope, and drives one turn. Every tool call passes throughavalancheTooling'stools/pre-executegate, which builds the policy request from the tool's risk profile and the network argument the call names. - SIMULATION. For read-only capabilities the simulation stage is empty by construction.
chain:simulateis an allowed capability class reserved for later fork simulation; no simulation adapter ships in this phase. - APPROVAL. A
require-approvalverdict parks the task inawaiting-approvalwith an approval record.avalanchePolicy.grant()performs an explicit operator request+decision;decide()refuses to overwrite a decision;consume()makes an approval single-use. Task resume after approval is Phase 5 work with the write execution path. - EXECUTION. In this phase execution is read-only tool execution. Write, sign, and infrastructure execution are deliberately absent; the policy engine denies them at the gate before any executor exists.
- VERIFICATION.
avalancheVerification.verify('chain-id', ...)asks the network registry what the endpoint reports and compares it with the descriptor's declared chain id. The verdict, detail, and observed facts are recorded. Unknown network, missing registry, or missing network on the task yieldsunverifiable, neververified. - EVIDENCE RECEIPT.
avalancheEvidence.issueReceipt()digests the receipt content (SHA-256 over canonical JSON of every other field), appends it to the ledger, and returns it. The receipt cites the evidence record ids that support it and carries the fixed disclaimer: it attests that policy evaluation passed and evidence is unmodified; it does not attest that model reasoning, generated code, or operator intent was correct.
Enforcement points
- Tool registration: only
avalancheTooling.register(spec)registers an Avalanche tool, and registration requires a risk profile.avalancheNetworks.registerTools()throws if the catalog is not mounted, so an uncatalogued tool cannot appear by accident. - Policy: the gate is a
tools/pre-executelistener registered inAvalancheTooling[Service.init]. It runs in the executor pipeline, after tool lookup and before dispatch, for every caller. It is not a prompt instruction, not schema filtering, and not a UI state. - Network: the RPC transport refuses any method not on
READ_ONLY_RPC_METHODS; the mainnet descriptor haswriteEnabled: false; network-bound policy resolves the network named by the tool argument at call time and denies unresolvable or unknown networks. - Domain tool masks:
AvalancheDomains.install(agentCtx, domainId)appliesctx.tools.restrict()inside the agent scope, so a domain cannot call tools outside its descriptor's allow/deny lists. - Secrets: the runtime never reads private keys and has no signing tool or key material in any schema. There is no field in the context, task, or receipt schema that can carry a private key.
Data and persistence
- Task records: one JSON file per task under
tasksDirwhen configured (theavalancheprofile derives$DSH_HOME/profiles/avalanche/avalanche/tasks), written through a serialized queue and reloaded at startup. - Evidence ledger: in-memory hash chain mirrored to
$DSH_HOME/profiles/avalanche/avalanche/evidence.jsonl; each record appends on commit in chain order; startup reloads and re-verifies the chain. - Receipts: stored both as queryable objects and as
receipt-kind ledger records, so they survive process restarts. - Sessions: upstream session persistence records the agent conversation; evidence records the execution facts. The two are linked by task id but never conflated.
Model-provider neutrality
Nothing in the Avalanche layer names a provider. The orchestrator reads agentDefaultModel.currentSelection() and passes it to agents.create; the model selection itself is an upstream concern (AgentDefaultModelConfig or a profile override). Domain routing is lexical, not model-based. A different adapter, a local model, or a mock all work without changing any Avalanche package.
What is deliberately not built
- No Next.js. The dashboard is Vite + React + TypeScript (Phase 4), and no Avalanche package depends on a server-side rendering framework.
- No microservice split. The modular boundaries are packages and services in one process; a service can become a process boundary later without changing its consumers.
- No write path, no signing, no key handling: denied by policy and absent from code.
- No fabricated chain data: every chain fact in a task originates from a tool result recorded by the executor, and the verification verdict is independent of the model's text.
Package dependency direction
avalanche-schemas is the leaf (types, digests, risk ordering). avalanche-context, avalanche-memory, avalanche-policy, and avalanche-verification depend only on schemas (and Cordis). avalanche-evidence depends on schemas and the tools event types. avalanche-tooling depends on policy, schemas, and tools. avalanche-network depends on tooling, schemas, tools, and verification. avalanche-agents depends on schemas. The seven domain packages depend on avalanche-agents. avalanche-orchestrator depends on agents, evidence, network, policy, and verification types. avalanche-cli depends on every service package because it reads all of them. avalanche-app depends on the whole set and ships only cordis.patch.yml.