Skip to content

Latest commit

 

History

History
333 lines (305 loc) · 26 KB

architecture.md

File metadata and controls

333 lines (305 loc) · 26 KB

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 to preview this document with the embedded HTML architecture diagram and PlantUML sequence diagram rendered.

Source map:

  • engine.rs: top-level Engine, EngineBuilder, exported APIs, management checks, hooks, challenge signing.
  • context/agent.rs: AgentCtx, local and subagent routing.
  • context/runner.rs: CompletionRunner, CompletionStream, queued input and compaction.
  • context/compaction.rs: agent-initiated compaction: SelfCompaction, the intrinsic compact_context call, admission and context-usage notes.
  • context/base.rs: BaseCtx, scoped state, cache, store, keys, HTTP, signed RPC, cancellation.
  • context/tool.rs: built-in discovery agents: tools_groups, tools_search, and tools_select.
  • model.rs: Models label router, provider adapters, retry and streaming helpers.
  • subagent.rs: reusable subagents, background sessions, compaction and handoff.
  • memory.rs: conversation/resource storage and KIP/Cognitive Nexus tools.
  • extension: 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.

Runtime View

<style scoped> .anda-arch{font-family:Inter,Segoe UI,Arial,sans-serif;color:#18202b;background:#f7f9fb;border:1px solid #d9e2ec;border-radius:8px;padding:18px;margin:16px 0;box-shadow:0 10px 28px rgba(24,32,43,.08)} .anda-arch *{box-sizing:border-box} .anda-title{font-size:22px;font-weight:760;letter-spacing:0;margin:0 0 4px;color:#101820} .anda-subtitle{font-size:13px;color:#52606d;margin:0 0 16px} .anda-layout{display:grid;grid-template-columns:minmax(168px,.75fr) minmax(420px,2.2fr) minmax(190px,.85fr);gap:12px;align-items:stretch} .anda-panel{background:#ffffff;border:1px solid #d9e2ec;border-radius:8px;padding:10px} .anda-panel-title{font-size:12px;text-transform:uppercase;letter-spacing:.04em;font-weight:780;color:#52606d;margin-bottom:8px} .anda-main{display:flex;flex-direction:column;gap:10px} .anda-layer{border:1px solid #d9e2ec;border-left-width:5px;border-radius:8px;background:#ffffff;padding:10px} .anda-layer.entry{border-left-color:#3b82f6} .anda-layer.control{border-left-color:#64748b} .anda-layer.registry{border-left-color:#22a06b} .anda-layer.runner{border-left-color:#d97706} .anda-layer.context{border-left-color:#0f766e} .anda-layer.model{border-left-color:#b45309} .anda-layer.storage{border-left-color:#7c3aed} .anda-layer-title{font-size:14px;font-weight:780;color:#18202b;margin-bottom:8px} .anda-grid{display:grid;gap:8px} .anda-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))} .anda-grid.three{grid-template-columns:repeat(3,minmax(0,1fr))} .anda-box{background:#f8fafc;border:1px solid #d9e2ec;border-radius:7px;padding:9px;min-height:58px;font-size:12px;line-height:1.35;color:#243b53} .anda-box strong{display:block;font-size:12.5px;color:#102a43;margin-bottom:3px} .anda-box small{display:block;color:#627d98} .anda-pill{display:inline-block;background:#edf2f7;border:1px solid #cbd5e1;border-radius:999px;padding:3px 7px;font-size:11px;color:#334155;margin:2px 3px 2px 0} .anda-side-list{display:flex;flex-direction:column;gap:7px} .anda-side-item{border:1px solid #d9e2ec;background:#f8fafc;border-radius:7px;padding:8px;font-size:12px;line-height:1.35;color:#334e68} .anda-note{margin-top:12px;border-top:1px solid #d9e2ec;padding-top:10px;font-size:12px;color:#52606d} @media (max-width:900px){.anda-layout{grid-template-columns:1fr}.anda-grid.two,.anda-grid.three{grid-template-columns:1fr}} </style>
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

@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.
  • 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_<agent>, tool paths use t_<tool>, 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<Message> of ContentPart) is the provider-neutral view that gets persisted and crosses the RPC boundary. raw_history (Vec<Json>) 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.
  • SubAgentManager turns persisted or temporary SubAgent definitions into callable SA_<name> 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. 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.

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