# 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).