# Anda Engine Architecture
This document describes the current `anda_engine` runtime architecture from the source code. It replaces the older deployment-first narrative that treated ICP, TEE, and IC-TEE as mandatory runtime blocks.
In the current engine, those integrations are optional backing capabilities. The practical center of the system is the `Engine` runtime: it validates callers, creates scoped contexts, dispatches agents and tools, routes model calls, handles tool-call loops, and exposes selected functions to callers.
Preview note: use [markdown-viewer/markdown-viewer-extension](https://github.com/markdown-viewer/markdown-viewer-extension) to preview this document with the embedded HTML architecture diagram and PlantUML sequence diagram rendered.
Source map:
- [`engine.rs`](../anda_engine/src/engine.rs): top-level `Engine`, `EngineBuilder`, exported APIs, management checks, hooks, challenge signing.
- [`context/agent.rs`](../anda_engine/src/context/agent.rs): `AgentCtx`, local and subagent routing.
- [`context/runner.rs`](../anda_engine/src/context/runner.rs): `CompletionRunner`, `CompletionStream`, queued input and compaction.
- [`context/compaction.rs`](../anda_engine/src/context/compaction.rs): agent-initiated compaction: `SelfCompaction`, the intrinsic `compact_context` call, admission and context-usage notes.
- [`context/base.rs`](../anda_engine/src/context/base.rs): `BaseCtx`, scoped state, cache, store, keys, HTTP, signed RPC, cancellation.
- [`context/tool.rs`](../anda_engine/src/context/tool.rs): built-in discovery agents: `tools_groups`, `tools_search`, and `tools_select`.
- [`model.rs`](../anda_engine/src/model.rs): `Models` label router, provider adapters, retry and streaming helpers.
- [`subagent.rs`](../anda_engine/src/subagent.rs): reusable subagents, background sessions, compaction and handoff.
- [`memory.rs`](../anda_engine/src/memory.rs): conversation/resource storage and KIP/Cognitive Nexus tools.
- [`extension`](../anda_engine/src/extension.rs): built-in tool libraries such as filesystem, shell, fetch, skills, notes, todos, and memory.
`TodoSession` is a bounded, shared in-memory task list seeded on the runner's
parent context. Handoff preserves its active tasks in a separate bounded
neutral-history message. Optional `NoteContextConfig` loads an agent-scoped note
index once per runner/window, gated by the request offering the local note tool
and the callable allowlist. The index is request-only context, replayed after a
model switch but not added to neutral history. These extensions use existing
tool hooks and object storage; they do not introduce a separate event service.
See [notes and tasks](note-todo.md).
## Runtime View
Anda Engine Runtime Architecture
Current source-level view: agents, tools, contexts, models, memory, and optional external capabilities.
Entrypoints
Host apps
CLI, HTTP server, bot runtime, or embedded Rust application call the engine API.
Public API
agent_runtool_callinformationchallenge
Remote callers
Clients can discover exported functions and call them through signed RPC.
Engine Boundary
EngineOwns runtime state, default agent, export lists, hooks, management policy.
EngineBuilderRegisters tools, agents, models, store, subagents, and hooks.
EngineCardPublishes exported agent/tool definitions for discovery clients.
Access Control and Observation
ManagementPrivate, protected, or public visibility; controller and manager principals.
Hookson_agent_start/end and on_tool_start/end can reject, observe, or transform outputs.
CancellationRoot and child cancellation tokens propagate through contexts and runners.
Callable Registries
AgentSetLocal agents, including built-in `tools_groups`, `tools_search`, `tools_select`, and `subagents_manager`.
ToolSet / ToolProviderSetStatic tools and runtime-discovered providers with function definitions, resource tags, and capability groups.
SubAgentSetSubagents routed with the `SA_` prefix, run inline or as background sessions.
AgentCtx and CompletionRunner
AgentCtxCombines BaseCtx with models, tools, agents, subagents, and routing helpers.
CompletionRunnerIterates model turns, executes tool calls, accumulates usage/artifacts, and returns final output.
SubAgent sessions`SA_` workers support blocking calls or background sessions with progress/final hooks.
BaseCtx Capability Surface
Scoped stateCaller, request meta, elapsed time, typed state extensions, and depth-limited children.
Store and cacheContext-path namespaces isolate agent and tool data in object store and cache.
External callsHTTP, signed RPC, and key derivation/signing through a configured Web3SDK.
Model Routing and Provider Adapters
ModelsLabel map plus primary model. Labels such as `pro`, `flash`, or `lite` choose provider entries.
AdaptersOpenAI-compatible, Anthropic, Gemini, and custom `CompletionFeaturesDyn` providers.
ReliabilityRequest defaults, SSE/NDJSON parsing, bounded retries with backoff, and retryable `ModelError` signals.
Optional Extensions and Persistence
Built-in toolsfetch, filesystem, shell, note, skill, todo, and memory tools register like any other tool.
MemoryConversation/resource records in AndaDB; KIP commands backed by Cognitive Nexus.
ObjectStoreIn-memory by default; local, cloud, or IC-COSE-compatible backends can be supplied.
External Capabilities
Model providers
Completion APIs are reached only through registered model adapters.
Web3SDK
Can be a TEE client, a Web3 client, or a not-implemented placeholder.
HTTP resources
Fetch and other outbound calls use the context HTTP/signed-RPC traits.
Databases
AndaDB and Cognitive Nexus are used when memory tools are registered.
Key point: the engine does not require a blockchain, TEE, or specific storage backend to schedule agents. Those are replaceable integrations behind `Web3SDK`, `Store`, model providers, or memory extensions.
## Request Sequence
```plantuml
@startuml
title Anda Engine agent_run and completion loop
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowColor #334155
skinparam sequenceLifeLineBorderColor #CBD5E1
skinparam sequenceParticipantBorderColor #CBD5E1
skinparam sequenceParticipantBackgroundColor #F8FAFC
skinparam sequenceGroupBorderColor #94A3B8
skinparam sequenceGroupBackgroundColor #F8FAFC
skinparam noteBackgroundColor #FFF7ED
skinparam noteBorderColor #FDBA74
actor Caller
participant "Host API\n(server / CLI / app)" as Host
participant "Engine" as Engine
participant "Management\n+ Hooks" as Guard
participant "AgentCtx\n+ BaseCtx" as Ctx
participant "Agent" as Agent
participant "CompletionRunner" as Runner
participant "Models\nlabel router" as Models
participant "Model adapter\nOpenAI / Anthropic / Gemini" as Model
participant "Tool / Agent\nregistries" as Registry
participant "Local Tool\nor Agent" as Local
participant "SubAgent\nsession runner" as SubSession
Caller -> Host : submit AgentInput
Host -> Engine : agent_run(caller, input)
Engine -> Engine : validate RequestMeta\nnormalize default agent
Engine -> Guard : check_visibility(caller)
Guard --> Engine : visibility accepted
Engine -> Ctx : ctx_with(caller, agent, label, meta)
Engine -> Guard : on_agent_start(ctx, agent)
Engine -> Agent : run(ctx, prompt, resources)
Agent -> Ctx : completion(req, resources)
Ctx -> Runner : completion_iter(req, resources)
loop until final output, failure, or cancellation
Runner -> Models : resolve(req.model or ctx.label)
Models --> Runner : Model
Runner -> Model : completion(CompletionRequest)
Model --> Runner : AgentOutput\ncontent, usage, raw_history, tool_calls
alt model returned tool_calls
Runner -> Registry : resolve each tool_call name\nlocal or SA_
par each resolved call
alt local tool
Runner -> Ctx : child_base(tool)
Ctx -> Local : Tool::call(BaseCtx, args, selected resources)
Local --> Runner : ToolOutput
else local agent or subagent without session
Runner -> Ctx : child(agent)
Ctx -> Local : Agent::run(AgentCtx, prompt, resources)
Local --> Runner : AgentOutput as ToolOutput
else subagent session mode
Runner -> Local : SubAgent::run(session args)
Local -> SubSession : claim session and spawn background runner
Local --> Runner : ack with session id
SubSession -> Guard : on_background_start
SubSession -> Runner : unbound completion loop
SubSession -> Guard : on_background_progress / on_background_end
end
end
Runner -> Runner : accumulate usage, artifacts, tools_usage\nappend tool outputs to next request
else no tool calls
Runner -> Runner : final_output()
end
opt steering or follow-up queued
Runner -> Runner : insert user content at safe boundary\nprune unanswered tool raw history if needed
end
end
Runner --> Agent : AgentOutput
Agent --> Engine : AgentOutput
Engine -> Guard : on_agent_end(ctx, agent, output)
Guard --> Engine : transformed output
Engine -> Engine : clear provider raw_history
Engine --> Host : AgentOutput
Host --> Caller : response
@enduml
```
## Component Notes
- `Engine` is the public runtime boundary. It enforces exported agent/tool lists for non-manager callers and always exports the default agent.
- `anda_engine_server` validates registry keys against engine identities, authenticates RPC bodies, and releases encoded parameters before awaiting execution. Discovery negotiates JSON/CBOR through `Accept`; caller-bearing responses are not cached. Public engine cards include current exported tool definitions. See the [HTTP and authentication contracts](../anda_engine_server/README.md#http-contract).
- `EngineBuilder` starts with in-memory storage, no implemented Web3 client, no external model, and built-in discovery/subagent control agents.
- `AgentCtx` is the main scheduling surface. It exposes local tools, dynamic tool providers, local agents, and subagents.
- `CompletionRunner` is iterative. A model turn can return tool calls; the runner executes them and feeds tool outputs into the next model turn. Long-running runners can compact oversized history into a continuation handoff and resume from that summary. Pending tool outputs are summarized with the history; pending request input, queued follow-up/steering, and the implicit context move to the replacement runner instead. A summary is committed only after validation, so failure leaves the original runner available for retry. When the compaction interrupted work the model had not finished (tool outputs folded into the summary) and nothing else is queued, the replacement runner resumes with `COMPACTION_CONTINUE_PROMPT`. Streams deliver queued input before committing normal completion and end after a failed step.
- Agent-initiated compaction (`set_self_compaction`) splits the decision from the mechanism. The model decides: from usage notes the runner adds once usage passes `notice_percent`, and from what the latest request still needs, it calls the runner-intrinsic `compact_context` with a handoff it writes itself. The runner admits it: only as the sole call of its response and above `min_percent`; otherwise the reason goes back as a tool error. The host executes it: an admitted request makes `needs_compaction_with` true, and `handoff` uses the model's text instead of a summary turn, re-delivering the latest user input verbatim when the model compacted before acting on it. The call is intercepted rather than registered as a `Tool`, because tools see only `BaseCtx`, whose state nested runners share. The usage note is a trailing request-history message, so it reaches the raw history but never the neutral `chat_history`. The forced 80% threshold and host overflow recovery remain the backstop.
- `tools_groups`, `tools_search`, and `tools_select` are agents, not side channels. `tools_groups` returns a compact directory of visible capability bundles; `tools_select` can expand one group into schemas, and discovered schemas stay in tool-output context while repeated payloads are compacted from conversation context.
- `BaseCtx` creates namespace-scoped child contexts. Agent paths use `a_`, tool paths use `t_`, and all store/cache operations are resolved under that path.
- `Models` routes by label first and then falls back to the primary/default model. Provider-specific names stay inside adapter configuration.
- Conversation history has two views, and the distinction is load-bearing. `chat_history` (`Vec` of `ContentPart`) is the provider-neutral view that gets persisted and crosses the RPC boundary. `raw_history` (`Vec`) holds the provider's own message JSON verbatim and exists so per-turn opaque state — Anthropic `thinking.signature`, Gemini `thoughtSignature` — survives a reasoning round without being modelled in `ContentPart`. Each turn the runner appends the response's `raw_history` onto the request's and clears `chat_history`; every adapter sends `raw_history` before the converted `chat_history`. The engine clears it at the RPC boundary, so it is scoped to one in-process round. A resumed conversation replays from `chat_history` alone and legitimately has no provider intermediate state — adapters must tolerate that rather than emit a block the provider rejects. A runner with `set_transient_inline_data(true)` treats inline attachment bytes as task-scoped: they never enter `chat_history`, and `CompletionFeaturesDyn::prune_inline_media` replaces them in `raw_history` with a text note when the runner goes idle or the task is stopped.
- Completion adapters reject unfinished streams before exposing text or tool calls to the runner; the shared request retry policy handles these failures. Gemini preserves signed Part boundaries in native history while joining adjacent text fragments in the neutral view. OpenAI and Anthropic assign paired IDs to ID-less neutral tool histories before conversion, leaving existing IDs and native history unchanged. Provider parameter and schema checks happen before sending the request; see the [adapter behavior](../anda_engine/README.md#models).
- `SubAgentManager` turns persisted or temporary `SubAgent` definitions into callable `SA_` agents. Long-running subagent sessions are keyed by caller principal, host-created root scope, and session ID. Runtime identities retain root/parent ownership; per-turn completion events are separate from session closure. Root-scoped admission and budgets are shared through context state. Optional idle checkpoints restore neutral history under current host capabilities; audit recording remains separate. See [subagent lifecycle](subagents.md). They use hooks with consistent task IDs to push progress and final output; priority stop/cancel controls interrupt pending work.
- Memory is an extension layer. Conversation/resource storage uses AndaDB collections, and persistent knowledge operations are exposed as KIP tools backed by Cognitive Nexus.
- Web3, TEE, ICP, and IC-COSE integrations are implementation choices behind `Web3SDK`, `HttpFeatures`, `KeysFeatures`, `CanisterCaller`, or `ObjectStore`. They are not mandatory architecture layers for the engine itself.
- `anda_web3_client` supplies optional `client` and `tee` backends. The generic client needs an explicit identity or root secret; identity-only clients cannot derive keys. Its default IC agent verifies queries, and an injected agent must share the client principal. Both backends use `anda_core::cbor_rpc` for bounded signed-RPC responses while retaining their own signing mechanisms. See the [client configuration](../anda_web3_client/README.md#configuration).
## Workspace execution and editing
`extension::workspace` exposes opt-in registration bundles. `coding_tools` combines
`ShellCommandTool`, `ShellSessionTool`, and `ApplyPatchTool` for the same local
workspace. Read/search/list operations remain shell commands in this bundle.
`readonly_file_tools` and `file_tools` support hosts that do not grant execution.
Existing `ShellTool`, filesystem tools and provider-neutral core contracts remain
available; no default engine export or management permission is broadened.
Session execution is an optional capability on `shell::Executor`. `NativeRuntime`
owns admission, scoped lookup, output buffers, a capped raw log, process deadlines,
and retention. Host-installed `ShellSessionScope` capabilities propagate through
context state; RPC metadata cannot create one. Lookups additionally verify engine,
caller and agent. Input and polling on one process are serialized; different
sessions can run concurrently. As with the legacy shell, hooks publish start,
progress and completion only for commands still running after the foreground
wait, with bounded hook waits so supervision stays responsive. Scoped
cancellation and runtime teardown stop processes. On Unix, session completion
terminates descendants remaining in the launched shell's process group before
draining output. The legacy shell path keeps its previous background lifecycle
for compatibility.
An optional immutable `SandboxPolicy` transforms both legacy and session launches
into platform-sandboxed commands. macOS uses Seatbelt and Linux uses bubblewrap;
unsupported or unavailable backends fail closed. Platform runtime files, declared
roots and networking are explicit host policy. Tool arguments cannot grant more
permissions. Application approval UX, secret configuration and remote execution
remain outside this extension. PTY allocation is opt-in on Unix; the native Windows
executor uses pipes, and Windows process isolation requires a custom executor.
Filesystem reads use opened, validated regular-file handles and bound actual bytes.
Cooperating edit/write/patch calls share bounded lock stripes. Unix writes use a
pinned directory descriptor for temporary creation and atomic rename; Windows
pins directory handles and validates reparse points and link counts. Patch parsing,
path resolution, exact matching, encoding, size limits and optional SHA-256
preconditions are validated before commit. All involved stripes are acquired in
stable order. Each file replacement is atomic; multi-file operations can partially
succeed and report which operations committed. Neither these locks nor optimistic
version checks are a transaction against external editors or shell commands.
## Skill catalogs
`extension::skill::SkillManager` owns a single published registry: immutable catalog
entries and their unambiguous delegated callables are replaced under one lock. Root/path
identities are distinct from frontmatter names and callable names. Host filters apply
before precedence and conflict resolution. Publication retains sessions only for the same
winning identity; renames, deletions and vanished roots remove old registrations.
A serialized refresh scans bounded directory trees, reads files through the shared
no-follow file access layer, parses frontmatter and optional `agents/openai.yaml`, then
publishes a generation with bounded diagnostics. No unbounded parse cache or per-read
whole-tree scan is retained. Hosts explicitly reload or invalidate after file notifications;
known reads validate the selected document and sidecar, and missing names trigger reload.
Incomplete scans publish verified entries and expose errors/truncation to the host.
The static `skills` tool group contains the compatible name reader `skills_manager`,
pageable `skills_list`, and package-contained `skills_read`. Resource cursors bind identity,
metadata and content fingerprints and always recheck current admission. Responses are
bounded after JSON serialization; resident catalogs shorten descriptions before omitting
entries. Tools use normal discovery, schema merging, typed hooks and cancellation, without
adding special completion history or a dynamic tool provider. Host snapshots expose
presentation, explicit-only selection policy and dependency preflight metadata. None of
these metadata fields expands the caller's execution permissions or installs providers.
See [the engine skill guide](../anda_engine/README.md#skill-catalogs-and-resources) for
limits, refresh semantics and integration details.
## MCP catalog and result lifecycle
Each MCP registration owns connection/refresh locks, catalog authority, call
concurrency, and cancellation. Fetches retain their registration identity until
publication, which checks both registration and session identity. A cancelled
fetch restores dirty state. Published raw tool metadata is retained alongside
model definitions; local names remain stable for the registration lifetime.
Calls refresh dirty catalogs, revalidate the captured route, and hold catalog
authority through execution. Removal cancels old work; disconnect retires queued
work while allowing requests already sent to finish.
MCP transport/lifecycle and protocol decoding remain in rmcp. Byte readers and
an HTTP backend bound input before decoding; pagination and model presentations
have separate budgets. The runner writes `ToolOutput.model_output` into the
existing neutral tool-output history envelope while retaining full raw results
for callers. Adapters preserve tool IDs and project supported media within the
tool response, with text fallback for unsupported API/model combinations.
Application callbacks supply optional elicitation; resource methods use explicit
server/URI selection. Approval UX, secret expansion, persistent credential stores
and launch policy remain outside the MCP protocol layer. No full conversation
history is sent to MCP servers. See [MCP integration](../MCP_INTEGRATION.md).