The product & architecture guide

AOS in Fifteen Minutes

If you cloned this repository five minutes ago, this page exists so that ten minutes from now you can answer every question in it without reading the codebase. Every claim carries the file that proves it.

618 tests green · CI-gated on every push · audited (production integrity · security · full inventory) · markdown twin: productguide/GUIDE.md

1What is the governor?

The Governor (src/core/governor.ts) is the one object every unit of work passes through. It answers the two questions the rest of the agent stack never does: did the agent actually do the work it claims it did? and what did that cost — and was it allowed to spend it?

Governor.execute(spec) takes a RunSpec (goal, model, hard budget, checks) and drives it through one pipeline, with no fast path around any stage:

select guard reserve checkpoint run verify contain settle record earn trust

The governor holds no environment knowledge. It talks to four ports (src/core/ports.ts): BudgetPort (money), RuntimePort (execution), VerifierPort (truth), LedgerPort (record). Everything concrete — SQLite, git, providers, HTTP — lives in adapters.

The one architectural law: src/core/ must never know that any specific database, billing platform, or tenant exists. It is what lets the same governor run standalone on a laptop, embedded in a host product, or multi-tenant on a platform — by swapping adapters, never the core.

2How does a request flow, end to end?

There are two kinds of request. Keep them separate in your head — the repo does.

2a · A governed job — the full lifecycle

Doors: CLI aos run (src/cli/commands.ts), console POST /api/run (src/serve/server.ts), MCP aos_run (src/mcp/tools.ts), queue worker & scheduler.

User request"fix the failing test", $2.00Arrives at a door. The door builds nothing itself — it calls wire().
wire()src/cli/wire.tsThe composition root. May swap cfg.model (subscription fallback) and cfg.cwd (worktree isolation) — callers MUST read the returned w.cfg, not their input.
SELECTsrc/select/complexity.tsThe selector picks the model (opt-in — §3) and records why.
GUARDUnfundable → denied free. Unpriceable → refused free. Fail closed, before any money moves.
RESERVEsrc/adapters/sqlite/budget.tsBudgetPort.reserve() — a lease on real funds, before any token. No lease, no run.
CHECKPOINTsrc/contain/Containment snapshot — git tag/stash, or a dedicated worktree: the operator's checkout is untouchable.
RUNsrc/runtime/loop.tsThe agent loop. Every tool call passes the permission gate (deny > plan > allow > risk-floor > mode; dangerous calls park on a human via src/approve/broker.ts). Every usage event is priced and drawn against the lease live — exhaustion is a hard abort.
VERIFYsrc/verify/CompositeVerifier — an AND, cheapest first: sentinel tripwires → the repo's own checks → an optional fresh-context judge asked to refute the claim.
CONTAINRefuted → rollback. Settled + isolated → integrate. Verified work is never dropped — a merge conflict preserves it on its branch.
SETTLETrue cost reconciled, remainder released — on every path: abort, crash-sweep, cancellation. Partial work settles at its metered cost, never $0.
RECORDOne ledger row (LedgerPort.append) + decision events (best-effort). Who, what, cost, verdict — forever.
EARN TRUSTThe (taskType, model) pair earns or loses autonomy (ledger.trust()). What ran well runs freer next time.
OutcomeThe door renders it; an isolated caller runs workspace.finish(outcome.state).
ObserveSSE events · JSONL trace (src/serve/trace.ts) · Prometheus /metrics.

2b · Governed inference — the gateway

Door: aos gatewaysrc/gateway/server.ts. Deliberately not the job lifecycle — a protocol adapter: OpenAI-compatible /v1/chat/completions where every call is reserved before the first upstream token, metered, settled exactly once (complete / disconnect / fail), and audited — but never verified or contained (passed: null on its ledger rows). Tool calls are returned to the client, never executed.

If you find yourself wanting verification at the gateway, you want a job (§2a), not a completion.

3Where are decisions made?

Routing happens in src/select/complexity.ts, fed by:

  • a complexity score (src/select/analyzer.ts — a pinned port of a production scorer),
  • blast radius (src/select/blast.ts — how dangerous the goal or tool call is),
  • novelty / institutional familiarity (src/select/novelty.ts — does this org's decision history know this kind of task?),
  • the trust pin — a proven (taskType, model) pair is kept, not re-rolled,
  • the budget step-down — a model the budget can't fund 20k tokens on is skipped,
  • the circuit breaker (src/resilience/breaker.ts) — downed models are routed around, UP first, never below the risk floor.

Order matters: pin → score → floors → budget → breaker → premium clamp. Risk and novelty floors route dangerous or unknown work up the ladder; the premium tier is only ever suggested, never auto-selected. Selection is opt-in — by default the requested model runs verbatim, and scheduled runs force selection OFF so the trust-gated pair is exactly what runs.

Mid-run escalation (cascade): a refuted verdict can re-run the same lease one rung up — opt-in, bounded, never after a sentinel alert. Tool-level decisions are made in the loop's permission gate and recorded per call — the enforced decision and the recorded decision are the same object.

4How are budgets enforced?

By construction, not by review — the money invariants live in the database:

  • Lease before workreserve() takes a hold before the first token, on every door.
  • Draw per usage event — each model call's cost is drawn live; exhaustion aborts the run. Unpriceable → abort, never $0.
  • Sub-leases — a sub-agent's allowance is carved from its parent's remainder; the SQLite row CHECK own_drawn + child_spend + subleased ≤ hold makes overdraw impossible, not unlikely. Queue trees ride the same mechanism — retries stop when the tree's money does.
  • Settle everything — overruns clamp at the hold (true bill recorded); cancellation settles partial spend; crashed runs are swept automatically by every long-lived process (src/cli/sweeper.ts) with an honest aborted ledger row.
  • Verification reserve — with a paid judge, work is capped at budget − reserve so the verifier can never be starved by the worker.
The rule that guards all of it: any code that reserves, draws, or settles money ships with tests — test/conformance/budget.ts, run against every adapter.

5Ledger vs. decision events

Two streams, one join key (runId), deliberately separate:

Money ledger (ledger)Decision events (decision_events, tool_events)
AnswersWhat happened, what did it cost, who did it, did it verify?Why was it routed / escalated / allowed that way?
Writesgovernor.finish() — exactly one row per run, append-onlygovernor + gateway — best-effort, schema-versioned
Failure modeFail closed — the money record is sacredFail open — analytics must never fail a run
DerivesTrust tiers, balances, /metricsEscalation stats, routing analytics, aos decisions
If they ever disagree, believe the ledger — the decision stream explains; it never authorizes.

6What are the extension points?

Everything extends at one of two seams — a port (new capability class) or the composition root src/cli/wire.ts (new implementation of an existing capability). The core never changes.

How do I add a model?

No code — models are configuration.

  • Cloud model: add pricing — "pricing": {"model-id": [in, out]} per Mtok. Unpriced cloud models are refused by design.
  • Local server (vLLM, llama.cpp, anything OpenAI-compatible): declare a provider — "providers": {"vllm": {"baseURL": "http://host:8000/v1", "local": true}}, then use vllm/<model>. Local ⇒ $0 (or localShadowPrice so offline budgets bite).
  • Ladder placement via "ladder" overrides. Never hardcode a model ID in src/core/.

How do I add a verifier?

Implement VerifierPort (src/core/ports.ts): verify(spec, evidence) → Verdict. Register it in the CompositeVerifier array in wire.ts — order is cost order; the composite is an AND and short-circuits. Study the two poles: src/verify/sentinel.ts (free tripwires) and src/verify/model.ts (paid judge, fails closed on unparseable output).

Rule: a verifier crash must abort the run (passed: null) — never pass it, never poison trust with a false rejection. Throw, and the governor handles it.

How do I add a tool?

Write a ToolDef (src/runtime/types.ts): name, description, JSON input schema, run(input, ctx) → string. Register in the default registry (src/runtime/tools/registry.ts) only if every run should have it; otherwise at wire time like the memory/web tools. Then classify it: add to SAFE_TOOLS/PLAN_ALLOWED (src/runtime/tools/permission.ts) only if genuinely read-only — an unlisted tool needs approval in auto mode, which is the safe default. Give it a blast-radius entry (src/select/blast.ts) if it can mutate anything, and make sure its artifacts are observed so the sentinel and judge can see them.

How do I add a routing signal?

Selector inputs are constructor deps of ComplexityModelSelector — the novelty scorer (src/select/novelty.ts) is the template: a (spec) → Promise<number> with a floor threshold. Apply it in select() beside the existing floors, then thread it through all three selector constructions: wire.ts, src/gateway/server.ts, and the console's route-preview — routing parity across doors is a stated invariant. Extend RoutingSignals (src/decide/types.ts) so the new signal lands on the decision record — never make a routing input that isn't recorded.

Other seams: a new budget/ledger/queue/memory/approval store implements the port and passes the matching suite in test/conformance/ (the suite is the contract). A new front door composes via wire() and respects the effective-config contract (w.cfg). A new judge engine uses the JudgePort seam.

7The map of the repo

LayerPathOne line
Coresrc/core/Governor, ports, money math, types. Knows nothing concrete.
Runtimesrc/runtime/The agent loop, providers (Anthropic + OpenAI-compat), 9 default tools, the permission lattice.
Selectionsrc/select/Complexity, blast radius, novelty, the ladder, the selector.
Verificationsrc/verify/Sentinel, deterministic checks, model judge, toolchain detection.
Containmentsrc/contain/Git rollback; worktree-per-run isolation.
Resiliencesrc/resilience/Circuit breaker, lease-bounded retry, failure classification.
Money & storessrc/adapters/sqlite/Budget (leases + CHECK), ledger, decisions, queue, schedules, users, approvals — 12 tables, migrations 1–10.
Other adapterssrc/adapters/In-memory references (conformance), platform budget adapter, subscription CLIs (claude/codex mux), the runtime adapter.
Doorssrc/cli/ · src/serve/ · src/gateway/ · src/mcp/CLI + console, OpenAI gateway, MCP server. src/cli/wire.ts is the composition root.
Colony extrassrc/queue/ · src/schedule/ · src/memory/ · src/ingest/ · src/approve/ · src/decide/Work trees, cron + trust gate, memory/RAG, approvals, the decision schema.
Deploydeploy/colony/ · infra/Docker stacks (the app) and host provisioning (the substrate).
Prooftest/ · docs/AUDIT-* · docs/SECURITY-REVIEW-* · docs/INVENTORY-*618 tests, CI-gated. The audits are part of the product.

8The fifteen-minute self-test

You're onboarded when these are obvious:

  1. What is the governor? The one pipeline all work passes through; four ports; no environment knowledge. (§1)
  2. How does a request flow? Door → wire → governor pipeline → outcome + records — or the gateway's reserve→meter→settle for bare inference. (§2)
  3. Where are decisions made? Selector (pre-run), cascade (mid-run), permission gate (per tool call) — all recorded. (§3)
  4. How are budgets enforced? Lease before token; live draws; DB-constraint sub-leases; settle on every path; auto-sweep. (§4)
  5. Ledger vs decision events? Money is fail-closed truth; decisions are fail-open explanation; joined on runId. (§5)
  6. Extension points? Ports + wire. Model = config. Verifier = VerifierPort + composite. Tool = ToolDef + registration + classification. Routing signal = selector dep + all three constructions + the decision record. (§6)

Then run it: aos setup → a $0.10 verified hello-world in five minutes; aos doctor when anything looks wrong. Deeper reading, in order: docs/SYSTEM-ARCHITECTURE.md (the why), docs/USAGE.md (every command), docs/INVENTORY-2026-07-18.md (the audited map of every capability), CLAUDE.md (the rules of the repo).

The design stance behind everything above: models and tools are interchangeable capabilities; governance is the durable asset. The core stays small so the record can be trusted; the record is trusted so autonomy can be earned; autonomy is earned so the system can be left alone.