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.
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:
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.
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.
wire().cfg.model (subscription fallback) and cfg.cwd (worktree isolation) — callers MUST read the returned w.cfg, not their input.BudgetPort.reserve() — a lease on real funds, before any token. No lease, no run.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.CompositeVerifier — an AND, cheapest first: sentinel tripwires → the repo's own checks → an optional fresh-context judge asked to refute the claim.LedgerPort.append) + decision events (best-effort). Who, what, cost, verdict — forever.ledger.trust()). What ran well runs freer next time.workspace.finish(outcome.state).src/serve/trace.ts) · Prometheus /metrics.2b · Governed inference — the gateway
Door: aos gateway → src/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.
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 work —
reserve()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 ≤ holdmakes 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 honestabortedledger row. - Verification reserve — with a paid judge, work is capped at budget − reserve so the verifier can never be starved by the worker.
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) | |
|---|---|---|
| Answers | What happened, what did it cost, who did it, did it verify? | Why was it routed / escalated / allowed that way? |
| Writes | governor.finish() — exactly one row per run, append-only | governor + gateway — best-effort, schema-versioned |
| Failure mode | Fail closed — the money record is sacred | Fail open — analytics must never fail a run |
| Derives | Trust tiers, balances, /metrics | Escalation stats, routing analytics, aos decisions |
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 usevllm/<model>. Local ⇒ $0 (orlocalShadowPriceso offline budgets bite). - Ladder placement via
"ladder"overrides. Never hardcode a model ID insrc/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
| Layer | Path | One line |
|---|---|---|
| Core | src/core/ | Governor, ports, money math, types. Knows nothing concrete. |
| Runtime | src/runtime/ | The agent loop, providers (Anthropic + OpenAI-compat), 9 default tools, the permission lattice. |
| Selection | src/select/ | Complexity, blast radius, novelty, the ladder, the selector. |
| Verification | src/verify/ | Sentinel, deterministic checks, model judge, toolchain detection. |
| Containment | src/contain/ | Git rollback; worktree-per-run isolation. |
| Resilience | src/resilience/ | Circuit breaker, lease-bounded retry, failure classification. |
| Money & stores | src/adapters/sqlite/ | Budget (leases + CHECK), ledger, decisions, queue, schedules, users, approvals — 12 tables, migrations 1–10. |
| Other adapters | src/adapters/ | In-memory references (conformance), platform budget adapter, subscription CLIs (claude/codex mux), the runtime adapter. |
| Doors | src/cli/ · src/serve/ · src/gateway/ · src/mcp/ | CLI + console, OpenAI gateway, MCP server. src/cli/wire.ts is the composition root. |
| Colony extras | src/queue/ · src/schedule/ · src/memory/ · src/ingest/ · src/approve/ · src/decide/ | Work trees, cron + trust gate, memory/RAG, approvals, the decision schema. |
| Deploy | deploy/colony/ · infra/ | Docker stacks (the app) and host provisioning (the substrate). |
| Proof | test/ · 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:
- What is the governor? The one pipeline all work passes through; four ports; no environment knowledge. (§1)
- How does a request flow? Door → wire → governor pipeline → outcome + records — or the gateway's reserve→meter→settle for bare inference. (§2)
- Where are decisions made? Selector (pre-run), cascade (mid-run), permission gate (per tool call) — all recorded. (§3)
- How are budgets enforced? Lease before token; live draws; DB-constraint sub-leases; settle on every path; auto-sweep. (§4)
- Ledger vs decision events? Money is fail-closed truth; decisions are fail-open explanation; joined on runId. (§5)
- 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.