AI Agent Assembly Documentation
Quick links to each component, its current version, and its license. Every version badge reads the latest published version live — core and Go from GitHub, Python from PyPI, Node from npm’s rc dist-tag — so they stay current with no manual updates. The project is in release candidate (v0.0.1-rc).
AI Agent Assembly is a governance layer for AI agents. It sits between your agents and the outside world and does three things:
- Enforces policy — decides, before each governed action runs, whether an agent is allowed to call a tool, reach a domain, or spend more budget.
- Tracks cost — meters token and dollar spend per team and blocks agents that exceed their budget.
- Intercepts unsafe actions — the sidecar proxy denies a routed call before it leaves the machine, the SDK advises in-process, and the Linux eBPF sensor detects activity without returning a verdict. Each is deployed on its own and covers only what it is on the path of.
Governance applies per agent, on the paths you wire up — you do not have to rewrite your agent’s logic, but each agent has to be launched through a governed path (an SDK your code initializes, or the sidecar proxy). An agent started outside those paths is not governed. See Known limitations for what is measured, unmeasured, and unsupported today.
Who this documentation is for
This site is for teams, security engineers, and operators evaluating or running AI Agent Assembly for production adoption.
If you are a developer who wants to contribute or integrate at the code level, see the open-source documentation instead.
Find what you need
Pick the page that matches what you are trying to do.
| I want to… | Go to |
|---|---|
| Govern an agent right now (runnable today) | Runnable examples |
| Read the design preview for managed SaaS onboarding (planned, not available) | Managed SaaS onboarding |
| Understand the security posture and threat model | Security model |
| Compare AI Agent Assembly to other tools | Why AI Agent Assembly? |
| Know what is open source vs. paid | Open core boundary |
| See what the managed control plane is intended to add (planned, not available) | Managed control plane |
| Look up a policy field or write a policy | Policy reference |
SDKs & components
This hub is the central entry point for AI Agent Assembly documentation. To instrument your agents, you install the SDK for your language — each one ships its own documentation site. Use the table below to go to the SDK that matches your codebase (Python, Node/TypeScript, or Go), or to Arena, the cross-framework governance trial ground.
Every module’s docs are aggregated into this hub under a stable subpath (/core/, /python-sdk/, /node-sdk/, /go-sdk/, /arena/), so you can read and search all of them from one place. The standalone, per-version sites remain available for release-specific (mike / Docusaurus / Hugo channel) browsing.
| Component | On this hub | Standalone site |
|---|---|---|
| Core (monorepo) | /core/ | core docs |
| Python SDK | /python-sdk/ | https://docs.agent-assembly.com/python-sdk/ |
| Node SDK | /node-sdk/ | https://docs.agent-assembly.com/node-sdk/ |
| Go SDK | /go-sdk/ | https://docs.agent-assembly.com/go-sdk/ |
| Arena | /arena/ | https://docs.agent-assembly.com/arena/ |
Runnable examples
Prefer learning by running code? The examples repo collects small, framework-specific Agent Assembly examples for Python, Node.js/TypeScript, Go, policy enforcement, approvals, audit, trace, and runtime workflows. Clone it and run an example end to end to see governance in action before instrumenting your own agents.
The interception mechanisms
Governance is assembled from independently-deployable interception mechanisms, and a deployment runs whichever subset it installs. They are not a fallback chain and not a ranking: each reaches a different claim level, and a mechanism you do not deploy is reported as absent rather than picked up by another. The numbering below is presentational and implies no order of precedence:
- SDK layer (in-process) — the language SDK wraps your agent’s framework tool calls and raises on a deny before the wrapped call runs. Fastest path, but advisory: it requires you to adopt the SDK and call its initializer, a non-cooperating process simply never calls it, and it does not intercept raw HTTP, subprocess spawns, or file access. Treat it as defense-in-depth, not the gate.
- Sidecar proxy (
aa-proxy) — intercepts outbound HTTP/1.1 that is routed to it, using per-host certificates minted from a local root CA, so it can govern agents that do not use the SDK. No agent code changes, but the process must honourHTTP_PROXY/HTTPS_PROXYand trust the CA (on macOS the install is attempted at proxy start viasecurity add-trusted-cert, which requires admin authorization — macOS prompts, and a refusal fails proxy startup; on Linux runsudo aasm proxy install-ca; Windows is unsupported). On MitM’d hosts, HTTP/2, gRPC, and WebSocket cannot be inspected — on other hosts they are tunnelled uninspected. - eBPF sensor (
aa-ebpf) — kernel hooks that watch OpenSSL and process syscalls. Observe-only: it reports what it sees and is consulted in no allow/deny decision, so its claim level is Observed and Detected — never prevention. It does not block, and it is not a mechanism the others fall back to. Linux only (the file-I/O kprobes are x86_64-only), and it degrades rather than failing closed if it cannot attach.
Each mechanism reports to the gateway, which evaluates policy and tracks per-team budgets. Coverage is the union of the layers you deploy, bounded by each layer’s own precondition — see Known limitations.
These interception points describe where a decision is applied; they sit inside the Boundary layer of the broader five-layer defense model, which describes what is protected. Same system, two views.
Clone the examples repo and run a governed LangChain
agent end to end — the path you can run today. The managed control
plane is planned, not available.
Last reviewed: 2026-06-27 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Documentation
AI Agent Assembly ships as five independently versioned programs, each with its own documentation site. This page is the central router — pick the program you are working with and follow the link to its docs.
Each documentation site has its own version selector — use it to view a specific release, a pre-release, or an older version.
Every link below points at the component’s site root, which always redirects to its newest stable release, so these references never need maintenance.
Core
- agent-assembly — the core monorepo: gateway, policy engine, eBPF sensor, sidecar proxy, FFI, WASM, CLI, and API.
SDKs
- Python SDK — install and govern agents from Python.
- Node SDK — install and govern agents from TypeScript or JavaScript.
- Go SDK — install and govern agents from Go.
Also on this hub
- Arena — watch AI agents from different frameworks attempt real and adversarial scenarios while agent-assembly enforces every decision, and read the published match reports.
Framework-specific end-to-end walk-throughs for Python, Node, and Go — a fast alternative to reading each SDK's docs top-to-bottom.
Last updated: 2026-07-11 by AI Agent Assembly Team
What ships today
What governs this page
This page is an inventory, not a promise. It answers one question — what is available right now, and on what evidence — and it answers it from a machine-readable source rather than from prose.
| Source | What it decides for this page |
|---|---|
The capability and evidence manifest (AAASM-5531), in the agent-assembly repository | Every row in every table below. Nothing inside a generated block is hand-written |
| ADR 0033 §6 | The eleven words for what the product did to an action. This page prints them and does not define them — Check a published claim names them and links their definitions |
| Product promise | The approved wording, the default-posture table, and which term each mechanism reaches. This page does not restate any of the three |
| Source of truth & status | Which repository owns an area of documentation, and how finished that area is |
Ticket references on this page are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
Every table below is generated from
governance/capability-manifest.yamlin theai-agent-assembly/agent-assemblyrepository — the capability and evidence manifest defined by AAASM-5531 — through the pinned extractcapability-surface.tomlin this repository.Manifest version
1.0.0· 80 capability rows · taken at commite2730ddaf· the manifest’s own evidence tree299de3883, dated 2026-08-06 · declared fix version agent-assembly v0.0.1-rc.7.The extract is refreshed by hand, so it can lag the manifest. What this repository’s CI proves is that the generated tables on these pages match the extract — prose outside the generated blocks is not checked, and proving the extract itself matches the manifest needs a cross-repository check, which is AAASM-5600.
Two different words are spelled Planned, and this page prints one of them. In the tables below it is ADR 0033 §6’s term, about an action: decided but not implemented, carrying a ticket reference and no capability claim. The 🗺️ Planned beside a page title in the sidebar is a documentation-area label from the status map, about how finished an area of this documentation is. Neither licenses a conclusion about the other: a
🧪 Release candidatearea can be Unsupported on a platform, and a shipped feature can be Unmeasured on a path.
Level 1 — one sentence
Agent Assembly ships a governance decision point and an evidence trail for actions you have routed onto a governed path, on Linux and macOS, and this page lists what each area reaches and where it stops.
Level 3 — for an evaluator
What each claim term covers today
Every manifest row carries exactly one ADR 0033 §6 term. Two of §6’s eleven have no row at all, and that is shown rather than left out — their absence is a fact about the manifest, not a hole in this table.
| ADR 0033 §6 term | Rows | Areas | Capability ids |
|---|---|---|---|
| Observed | 5 | Developer-tool launch, Host actions, Platform host-level interception, SDK and framework seams | S3 S4 H4 L5 P2 |
| Detected | 2 | Credentials, Host actions | H2 C6 |
| Evaluated | 11 | Degraded and failure modes, Identity and attribution, Network traffic, SDK and framework seams | S6 S9 S13 N4 I1 I2 I3 I5 I7 G5 G8 |
| Denied before execution | 13 | Degraded and failure modes, Developer-tool launch, Host actions, MCP, Network traffic, SDK and framework seams | S1 S2 S5 S8 H5 N1 N2 N3 M1 M3 L1 G1 G3 |
| Redacted | 5 | Credentials, Degraded and failure modes, MCP, Network traffic | N7 M9 C1 C2 G4 |
| Approval required | 0 | — | — no row in the manifest carries this term |
| Degraded | 1 | Degraded and failure modes | G6 |
| Unmeasured | 36 | Credentials, Degraded and failure modes, Developer-tool launch, Host actions, Identity and attribution, MCP, Network traffic, SDK and framework seams | S7 S10 S11 S12 H1 H3 H6 H7 H8 N5 N6 N9 N10 N12 N13 M2 M4 M5 M6 M7 M10 L2 L3 L6 L7 L8 C3 C4 C5 I4 I6 G2 G7 G9 G10 G11 |
| Experimental | 1 | Platform host-level interception | P1 |
| Planned | 0 | — | — no row in the manifest carries this term |
| Unsupported | 6 | Developer-tool launch, MCP, Network traffic, Platform host-level interception | N8 N11 M8 L4 P3 P4 |
| Total | 80 |
Approval required having no row does not mean approvals are absent from the product; it means no capability row’s outcome is recorded as that term. Planned having no row is what §6 asks for: the term carries a ticket reference and no capability claim, so it belongs beside a ticket rather than in an inventory of what exists.
Where those outcomes sit
| Area | Rows | Outcomes its rows reach today |
|---|---|---|
SDK and framework seams (sdk) | 13 | Observed 2 · Evaluated 3 · Denied before execution 4 · Unmeasured 4 |
Network traffic (network) | 13 | Evaluated 1 · Denied before execution 3 · Redacted 1 · Unmeasured 6 · Unsupported 2 |
MCP (mcp) | 10 | Denied before execution 2 · Redacted 1 · Unmeasured 6 · Unsupported 1 |
Host actions (host_action) | 8 | Observed 1 · Detected 1 · Denied before execution 1 · Unmeasured 5 |
Developer-tool launch (devtool_launch) | 8 | Observed 1 · Denied before execution 1 · Unmeasured 5 · Unsupported 1 |
Credentials (credentials) | 6 | Detected 1 · Redacted 2 · Unmeasured 3 |
Identity and attribution (identity) | 7 | Evaluated 5 · Unmeasured 2 |
Degraded and failure modes (degraded_mode) | 11 | Evaluated 2 · Denied before execution 2 · Redacted 1 · Degraded 1 · Unmeasured 5 |
Platform host-level interception (platform) | 4 | Observed 1 · Experimental 1 · Unsupported 2 |
Host actions are shell and subprocess, files, browser automation and database queries. The other area names carry their scope on their face; the manifest’s own enum value is printed beside each so a rename cannot hide in a display label.
Read the counts as counts of manifest rows, not of features and not of code paths. A row is one question the manifest asked and answered; an area with more rows was examined in more places, not necessarily covered in more places. The per-mechanism version of this question — which term each named mechanism reaches — is Product promise’s, and is not repeated here.
Platforms
Host-level interception is per platform, and a platform without an adapter has none. There is no lower mechanism that picks up what an absent one would have done, and eBPF is one Linux mechanism rather than a cross-platform floor.
| Platform | Capability rows released on it | Host-level interception today | Reachability of that row |
|---|---|---|---|
| Linux x86_64 | 71 | Experimental (P1) | shipped on crates.io only |
| Linux aarch64 | 70 | Observed (P2) | shipped on crates.io only |
| macOS | 64 | Unsupported (P3) | shipped on crates.io only |
| Windows | 13 | Unsupported (P4) | no mechanism exists |
The authoritative platform matrix — including what transport mediation reaches on each platform, and the macOS row, which ends with an instruction not to read it as no host enforcement on macOS — is ADR 0033 §5.3. The table above is the manifest’s view of the same question and defers to it.
Where the artifacts come from
Distribution is per channel and per platform: a capability can ship on one channel and not another, and absent from a list is not the same statement as not shipped there. The manifest can record the second only for the container-image channel today. That mechanism was added by AAASM-5680 and has not been extended to the other seven.
| Channel | Rows delivered on it | Rows recorded as not published there | Rows recorded as not surveyed |
|---|---|---|---|
GitHub Release assets (github_release) | 50 | — not recorded | — not recorded |
Homebrew tap (homebrew) | 50 | — not recorded | — not recorded |
Install script (install_script) | 50 | — not recorded | — not recorded |
crates.io (crates_io) | 73 | — not recorded | — not recorded |
PyPI (pypi) | 13 | — not recorded | — not recorded |
npm (npm) | 13 | — not recorded | — not recorded |
Go modules (go_modules) | 13 | — not recorded | — not recorded |
GHCR container images (ghcr) | 24 | 32 | 17 |
No distribution question (not_applicable) | 7 | — not recorded | — not recorded |
Channels the manifest surveyed: github_release, homebrew, install_script, crates_io, pypi, npm, go_modules, ghcr, not_applicable. Channels it did not survey: none.
So on every channel except ghcr, a row that does not list a channel is telling you
only that it does not list it. Do not read a not recorded cell as a zero. Which
container images exist, and how their tags move, is
Docker & containers’s.
What stands behind the rows
A capability row is worth what its evidence is worth. The manifest separates a test it can point at from a test it was told exists but could not locate, and separates both from a recorded gap — then separates all three from whether that evidence actually runs.
| What stands behind the row | Rows |
|---|---|
| At least a located test | 26 |
| No located test, but a test asserted but not locatable from the manifest’s repository | 10 |
| A recorded gap — no test | 44 |
| Does that evidence run? | Rows |
|---|---|
It runs on every push to main | 42 |
| It is path-gated, with a schedule | 5 |
| It does not run | 33 |
A recorded gap is not a silence. It is the manifest saying, in the row itself, that no test backs this and why. To take any single published sentence to the evidence behind it, start at Check a published claim.
What this page does not answer
- Whether a control is on. Shipping, buildable and activated are three separate questions. A capability can be in an artifact you installed and still be off, or reachable only when an environment variable names a process.
- Whether your agent is on a governed path. Coverage is a per-host, per-launch fact rather than a property of the architecture. Start at Choose your enforcement path.
- What a term means. ADR 0033 §6 defines the vocabulary; this page prints it.
- How finished an area of documentation is. That is the status map’s maturity label, on a different axis from anything here.
- Whether the extract is current with the manifest. This repository’s CI proves the pages match the extract. Nothing here proves the extract matches upstream; that check is AAASM-5600.
The managed service is not in these numbers
The Cloud control plane and the Enterprise operations features are documented as intent. Their documentation areas carry the status map’s 🗺️ Planned label, they have no rows in the capability manifest, and nothing on this page should be read as saying that either can be provisioned or operated today. What is written about them describes a design. Where the line between the open and the commercial side falls is the Open core boundary’s to state.
Deeper
The implementation-level answer — which crate does what, at which source line, and the
highest term each mechanism can legitimately reach — is
ADR 0033
in the Core documentation. The manifest this page is generated from is
governance/capability-manifest.yaml.
Generated content on this page is rendered by
docs/scripts/generate_capability_surface.pyfromcapability-surface.toml. Do not hand-edit between theBEGIN/END GENERATEDmarkers — AAASM-5609.
Last reviewed: 2026-08-13 — AI Agent Assembly Team
Last updated: 2026-08-20 by AI Agent Assembly Team
Choose your enforcement path
What governs this page
Three governed paths exist, they are independent of one another, and choosing between them is a decision about your platform and launch model rather than about the product’s architecture. This page sets out what each one needs, when it decides, what it does when it fails, and — at the same length — what it does not cover.
| Source | What it decides for this page |
|---|---|
The capability and evidence manifest (AAASM-5531), in the agent-assembly repository | Every row in every table below |
| ADR 0033 §1 | The roles the three paths are named after, and which components implement each |
| ADR 0033 §6 | The words for what a path did to an action. This page prints them and does not define them |
| What ships today | The whole-product inventory, the platform position, and the distribution channels |
Ticket references on this page are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
Every table below is generated from
governance/capability-manifest.yamlin theai-agent-assembly/agent-assemblyrepository — the capability and evidence manifest defined by AAASM-5531 — through the pinned extractcapability-surface.tomlin this repository.Manifest version
1.0.0· 80 capability rows · taken at commite2730ddaf· the manifest’s own evidence tree299de3883, dated 2026-08-06 · declared fix version agent-assembly v0.0.1-rc.7.The extract is refreshed by hand, so it can lag the manifest. What this repository’s CI proves is that the generated tables on these pages match the extract — prose outside the generated blocks is not checked, and proving the extract itself matches the manifest needs a cross-repository check, which is AAASM-5600.
Level 1 — one sentence
Pick a path by what you can change — your agent’s code, its network route, or its host — then read what that path does not cover before you plan around it.
Level 3 — for an evaluator
The three paths, side by side
Grouping the manifest’s rows onto these three is this page’s doing; every fact rendered under a group is the manifest’s. The grouping follows the roles ADR 0033 §1 names and the components it lists against each. It is deliberately not a partition of the whole manifest — the rows no path claims are listed further down rather than forced into one.
| Path | Implemented today by | Rows | Outcomes its rows reach | Rows at Unmeasured or Unsupported |
|---|---|---|---|---|
| Managed execution checkpoints | the SDK seams, aa-runtime’s handle_policy_query, aa-sdk-client and aa-sandbox | 17 | Observed 2 · Evaluated 4 · Denied before execution 6 · Unmeasured 5 | 5 of 17 |
| Protocol and transport mediation | aa-proxy | 26 | Evaluated 1 · Denied before execution 6 · Redacted 5 · Unmeasured 11 · Unsupported 3 | 14 of 26 |
| Platform-specific host-level interception adapters | Linux eBPF via aa-ebpf-loaderd; on macOS and Windows, no adapter | 11 | Observed 2 · Detected 1 · Degraded 1 · Unmeasured 4 · Experimental 1 · Unsupported 2 | 6 of 11 |
The paths do not form a chain. A path you do not deploy is absent, and its absence is a reportable state rather than something the next one picks up. Deploying two does not compose them into a stronger single control; it gives you two controls with two separate boundaries.
What is not a path
Three things are routinely mistaken for a fourth path, and each mistake changes what you would plan for:
- The control plane is not an interception point. It holds policy, identity, budgets, approvals and audit, and it answers decision requests — but no agent bytes traverse it, so a refusal it issues stops something only when a component in front of the action blocks on the answer. Which components do, and which refuse on their own local configuration instead, is ADR 0033 §2’s caller table. Attribute a refusal to whichever component actually made it.
- eBPF is one Linux mechanism, not a cross-platform floor. It is an implementation of the host-adapter role on Linux only, and on the other platforms nothing takes its place. See what ships today.
- Writing a tool’s own settings file is not data-path mediation. It is tool-governance: it takes effect only if the tool honours those keys. Any data-path refusal such an integration delivers is the proxy’s, borrowed through the launch environment the integration injects. These rows are listed separately for that reason.
Path 1 — Managed execution checkpoints
What you change: your agent’s code, or the framework adapter it initialises. The checkpoint is a call your process makes before running a tool.
Who it suits: teams that own the agent’s source and can adopt an SDK.
| Implemented today by | the SDK seams, aa-runtime’s handle_policy_query, aa-sdk-client and aa-sandbox |
| Manifest rows | 17 — S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 H5 G1 G2 G5 |
| Outcomes its rows reach | Observed 2 · Evaluated 4 · Denied before execution 6 · Unmeasured 5 |
| Decision timing | before the action takes effect 12 · after the action 2 · no decision point 3 |
| Failure posture | fail closed 9 · fail open 1 · fail open, and silently 2 · no failure posture 5 |
| Reachability | shipped 17 |
| Default state of its controls | on 5 · off 2 · open 2 · closed 3 · mixed 1 · not_applicable 4 |
| Declared preconditions | env:AA_AGENT_ID |
| Launch paths that reach it | aasm sandbox run, or POST /dispatch_tool with ToolKind::Wasm · any · assembly_Init_plus_explicit_WrapTools · auto_detected_at_initAssembly · default_go_build · in_process_after_init · in_process_after_init_assembly · initAssembly_with_defaults · tools_passed_as_config_langchain_tools |
What this path does not cover. Every row below is one the manifest records at Unmeasured or Unsupported — nothing is known about the action, or the mechanism is not available at all.
| Id | What it is | ADR 0033 §6 term | Reachability |
|---|---|---|---|
S7 | Node default mode routes every policy check through an allow-all no-op client | Unmeasured | shipped |
S10 | Direct function call that does not pass a patched seam | Unmeasured | shipped |
S11 | Framework with no adapter | Unmeasured | shipped |
S12 | Raw HTTP, subprocess, filesystem, DB driver, browser automation from inside an SDK-adopting process | Unmeasured | shipped |
G2 | aa-runtime with no gateway configured, or fail_closed=false | Unmeasured | shipped |
Two properties of this path decide whether it fits, and neither is visible from a quickstart:
- The checkpoint is voluntary. It is a call the agent makes. A process that does not make it is outside the boundary, and the product knows nothing about that action — not that it was allowed, and not that it was clean.
- Honouring a refusal is the shim’s job, not the client’s. ADR 0033 §4 records that the in-repo decision helper has no caller that refuses to execute, and that one out-of-repo shim maps transport failures to allow. Read the per-language rows above before assuming a returned refusal stops anything.
Path 2 — Protocol and transport mediation
What you change: the agent’s network route — an injected proxy environment and a trusted certificate authority — rather than its code.
Who it suits: teams that cannot modify the agent, including closed-source and vendor tools, on a platform where the mediator runs.
| Implemented today by | aa-proxy |
| Manifest rows | 26 — N1 N2 N3 N4 N5 N6 N7 N8 N9 N10 N11 N12 M1 M2 M3 M4 M5 M6 M7 M8 M9 C1 C2 C4 G3 G4 |
| Outcomes its rows reach | Evaluated 1 · Denied before execution 6 · Redacted 5 · Unmeasured 11 · Unsupported 3 |
| Decision timing | before the action takes effect 5 · in line with the request 8 · no decision point 13 |
| Failure posture | fail closed 10 · fail open 6 · silent truncation 1 · no failure posture 9 |
| Reachability | shipped 3 · shipped, with a platform exception 20 · no mechanism exists 3 |
| Default state of its controls | on 6 · off 4 · open 3 · closed 1 · not_applicable 12 |
| Declared preconditions | env:AA_PROXY_DENIED_HOSTS · env:AA_PROXY_GATEWAY_ENDPOINT · env:AA_PROXY_LLM_ONLY · env:AA_PROXY_MCP_FAIL_OPEN · env:AA_PROXY_MITM_HOSTS · env:AA_PROXY_NETWORK_ALLOWLIST · env:AA_PROXY_PROVIDER_KEYS · env:HTTPS_PROXY |
| Launch paths that reach it | any · default_aa_proxy_run · provider keys configured in the proxy's environment · routed · routed through aa-proxy, CA trusted, gateway endpoint configured, request reaching a MitM'd non-LLM host · routed, CA trusted, AND llm_only=false or an operator mitm_hosts entry · routed_and_ca_trusted · same_as_M1 · traffic_routed_to_the_proxy |
What this path does not cover. Every row below is one the manifest records at Unmeasured or Unsupported — nothing is known about the action, or the mechanism is not available at all.
| Id | What it is | ADR 0033 §6 term | Reachability |
|---|---|---|---|
N5 | HTTPS to a host not under MitM | Unmeasured | shipped, with a platform exception |
N6 | Model response body scanning on LLM hosts | Unmeasured | shipped, with a platform exception |
N8 | HTTP/2, gRPC or WebSocket over a MitM’d host | Unsupported | shipped, with a platform exception |
N9 | Chunked transfer encoding | Unmeasured | shipped, with a platform exception |
N10 | Raw TCP that does not speak the proxy protocol | Unmeasured | shipped |
N11 | UDP, QUIC, HTTP/3 | Unsupported | shipped |
N12 | Local IPC (Unix domain sockets) between third-party processes | Unmeasured | shipped |
M2 | MCP enforcement with no gateway configured | Unmeasured | shipped, with a platform exception |
M4 | Every MCP method other than tools/call | Unmeasured | shipped, with a platform exception |
M5 | MCP over stdio (subprocess pipes) | Unmeasured | no mechanism exists |
M6 | MCP over SSE (text/event-stream) | Unmeasured | no mechanism exists |
M7 | MCP over Streamable HTTP | Unmeasured | shipped, with a platform exception |
M8 | MCP over WebSocket | Unsupported | no mechanism exists |
C4 | Model response credential scanning | Unmeasured | shipped, with a platform exception |
Three defaults on this path are worth setting expectations against before you plan:
- Inspection is opt-in per host, not blanket. Only a built-in set of provider hosts is intercepted by default; everything else is tunnelled through without its payload being looked at. Widening that is an operator decision with a latency and compatibility cost.
- The egress lists are empty out of the box. An allow/deny decision at connection time decides nothing until you populate them.
- The mediator is not packaged everywhere. On macOS it arrives through crates.io only; see the channel table in what ships today.
Path 3 — Platform-specific host-level interception adapters
What you change: the host — a privileged component installed beside the agent.
Who it suits: Linux operators who need a view of process, file and TLS activity that does not depend on the agent cooperating.
| Implemented today by | Linux eBPF via aa-ebpf-loaderd; on macOS and Windows, no adapter |
| Manifest rows | 11 — H2 H3 H4 N13 I4 G6 G7 P1 P2 P3 P4 |
| Outcomes its rows reach | Observed 2 · Detected 1 · Degraded 1 · Unmeasured 4 · Experimental 1 · Unsupported 2 |
| Decision timing | in line with the request 1 · after the action 7 · no decision point 3 |
| Failure posture | fail closed 1 · fail open 8 · fail open, and silently 1 · no failure posture 1 |
| Reachability | shipped on crates.io only 10 · no mechanism exists 1 |
| Default state of its controls | on 6 · off 2 · open 2 · not_applicable 1 |
| Declared preconditions | env:AA_EBPF_CONFINE_PID |
| Launch paths that reach it | AA_EBPF_CONFINE_PID set AND policy lowers a non-empty allowlist · any · none · privileged aa-ebpf-loaderd · privileged aa-ebpf-loaderd at /run/aa-ebpf-loaderd.sock · proxy start (CA install attempted automatically); managed-settings write |
What this path does not cover. Every row below is one the manifest records at Unmeasured or Unsupported — nothing is known about the action, or the mechanism is not available at all.
| Id | What it is | ADR 0033 §6 term | Reachability |
|---|---|---|---|
H3 | Process exec observation | Unmeasured | shipped on crates.io only |
N13 | TLS plaintext observation without the proxy | Unmeasured | shipped on crates.io only |
I4 | Process-tree identity across fork/exec | Unmeasured | shipped on crates.io only |
G7 | eBPF policy file unreadable or unparseable | Unmeasured | shipped on crates.io only |
P3 | macOS host-level interception | Unsupported | shipped on crates.io only |
P4 | Windows mediation | Unsupported | no mechanism exists |
This path is the one most often planned against optimistically:
- It is predominantly an observation mechanism. Its probes feed the evidence pipeline and are consulted in no allow/deny decision.
- Its one terminating control is off unless you name a process, and it acts after the offending call rather than before it — which is why the manifest records it at Detected rather than at a pre-execution refusal.
- There is no adapter on macOS or Windows, and the rows above say so rather than leaving it to inference. Read the macOS position together with ADR 0033 §5.3, whose macOS row carries a qualification a summary reliably drops.
The launch route, which is not itself a path
A managed launch is how an agent or developer tool is placed onto one of the paths above — it injects the environment, and it writes settings into the tool’s own configuration. Its rows are here rather than under a path because a settings write is not a claim about the data path.
| Id | Route | ADR 0033 §6 term | Decision timing | Failure posture |
|---|---|---|---|---|
H8 | Shell / file rule declared in a tool’s own settings file | Unmeasured | before the action takes effect | fail open, and silently |
M10 | MCP-server governance by configuration | Unmeasured | before the action takes effect | fail open, and silently |
L1 | Claude Code managed launch | Denied before execution | before the action takes effect | fail closed |
L2 | Codex managed launch | Unmeasured | before the action takes effect | fail open, and silently |
L3 | Windsurf managed launch | Unmeasured | before the action takes effect | fail open, and silently |
L4 | Copilot managed launch | Unsupported | no decision point | no failure posture |
L5 | SaaS / opaque agent | Observed | after the action | no failure posture |
L6 | Unmanaged launch — the user starts the tool directly | Unmeasured | no decision point | no failure posture |
L7 | Settings-layer governance surviving an unmanaged launch | Unmeasured | before the action takes effect | fail open, and silently |
L8 | aasm run –no-proxy | Unmeasured | no decision point | no failure posture |
Supported tool and governable tool are different lists. One shipped adapter returns a launch failure rather than a governed command, and another is capped at observation; for those, no proxy environment is injected and there is nothing to route into.
Rows no path above claims
These are the manifest rows the three paths do not account for. They are published rather than dropped, because a row that quietly belongs to no path is exactly the kind of coverage assumption this page exists to prevent.
| Area | Rows | Outcomes they reach | Capability ids |
|---|---|---|---|
Host actions (host_action) | 3 | Unmeasured 3 | H1 H6 H7 |
Credentials (credentials) | 3 | Detected 1 · Unmeasured 2 | C3 C5 C6 |
Identity and attribution (identity) | 6 | Evaluated 5 · Unmeasured 1 | I1 I2 I3 I5 I6 I7 |
Degraded and failure modes (degraded_mode) | 4 | Evaluated 1 · Unmeasured 3 | G8 G9 G10 G11 |
| Total | 16 |
Most are cross-cutting — identity, credentials and the control plane’s own failure modes apply to whichever path you pick. The host-action rows are not: they are actions for which no mediation mechanism exists at all.
What none of these paths gives you
- A durable audit record from the SDK’s own interceptor. On the shipped path, a governed tool call through an SDK’s interceptor does not produce one — a caller-supplied handler does receive the record, but the SDK wires nothing itself. That capability is Planned in ADR 0033 §6’s sense: decided, not implemented, tracked as AAASM-5750, and carrying no capability claim until it lands. Audit evidence produced elsewhere in the system is a different question, and Check a published claim is how to take any single sentence about it to its evidence.
- A statement that an uninspected action was fine. Where nothing inspected an action, the honest report is that nothing is known about it. An absent audit entry is a fact about the observer, not about the agent.
- Coverage as a property of the architecture. It is a per-host, per-launch fact. Two hosts running the same version can have different answers.
- Anything from the managed service. The Cloud control plane and the Enterprise operations features are documented as intent, carry the status map’s 🗺️ Planned documentation-area label, and have no rows in the capability manifest. Nothing here should be read as saying either can be provisioned today.
Deeper
The role model these three paths are named after, the caller table behind the control
plane’s position, and the verified platform matrix are all in
ADR 0033
in the Core documentation. The manifest this page is generated from is
governance/capability-manifest.yaml.
Generated content on this page is rendered by
docs/scripts/generate_capability_surface.pyfromcapability-surface.toml. Do not hand-edit between theBEGIN/END GENERATEDmarkers — AAASM-5609.
Last reviewed: 2026-08-13 — AI Agent Assembly Team
Last updated: 2026-08-20 by AI Agent Assembly Team
Product promise & message hierarchy
This page is for anyone writing public copy about AI Agent Assembly — the product website, this hub, a README, a conference abstract, a sales deck. It exists because the product’s honest value is narrower than its architecture diagram suggests, and narrower still than the words the category usually reaches for. A reader who discovers an overstated claim after provisioning is a worse outcome than one who reads an accurate limit up front.
It is also for evaluators. Levels 1 to 3 below are the fastest accurate account of what the product does, what is on by default, and what it leaves uncovered.
What governs this page
This brief is downstream of two merged artifacts, and it does not add claims to them:
| Source | What it supplies | Where |
|---|---|---|
| ADR 0033 — Canonical Governance & Enforcement Architecture | The architecture, the platform matrix, and §6’s claim vocabulary. Every clause of the promise below is expressed in one of §6’s terms. | ADR 0033 in the core docs |
| AAASM-5528 — public claim inventory | 69 audited rows of what may and may not be said, each tied to a re-verified evidence block (E1–E7). | verification-reports/AAASM-5528-public-claim-inventory.md |
Ticket references on this page are plain text, not links: the tracker is not publicly readable, so a link would only take a reader to a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
Two further sources are chartered but not yet available, so nothing here depends on them: the current-state capability matrix (AAASM-5527, in progress) and the machine-readable capability/evidence manifest (AAASM-5531, not started). Statements that would need either of those to be verified are collected under Provisional rather than asserted.
Precedence. Where this hub still carries the superseded three-layer or “IronClaw five-layer” framing — Security model still carries the five-layer model, at the time of writing; AAASM-5727 removed the three-layer framing and the fallback-chain wording from it and from Glossary — ADR 0033 wins, and ADR 0033 records the gap as a tracked, accepted window. Do not resolve a conflict in the superseded model’s favour.
The two are not equally owned.
security-model.mdis named in ADR 0033’s migration checklist and is covered by AAASM-5586 / AAASM-5609.glossary.mdwas named by neither — not by the checklist and not by the claim inventory, whosedocsrows reach onlyREADME.mdandcomparison.md— so its residuals sat unowned while looking tracked. Now AAASM-5658, which covered two defects: the eBPF entry saying the sensor catches what the layers above miss — corrected by AAASM-5727 — and the SDK entry, which still says the SDK applies allow/deny decisions, contradicting the advisory finding. That ticket also adds the file to ADR 0033 §E, so the next sweep cannot miss it for the same reason this one did.Note also that this rule is one-directional — a reader who arrives at those pages first never sees it — so the migration tickets, not this note, are the fix.
Maturity labels (🧪 Release candidate, 🗺️ Planned) belong to Source of truth & status and answer a different question — how finished a feature is. ADR 0033 §6’s terms answer what the product did to an action, when, and on what evidence. The two are orthogonal: a 🧪 Release candidate feature can be Unsupported on a platform, and a shipped feature can be Unmeasured on a path. Cross-reference them; never let one redefine the other.
The promise
There is exactly one. Everything else on this page is a rendering of it at a different depth.
Agent Assembly decides whether an AI agent’s action is allowed before that action runs — on the paths you route through it — and records what was decided, so a risky call can be refused, or blocked pending a decision, instead of discovered afterwards.
Headline and subheadline
For a hero or a first screen, the promise renders as:
Headline
Decide what an AI agent may do — before it does it.
Subheadline
Agent Assembly evaluates the actions you route through it against your policy, refuses them, or blocks them pending a decision, and records what it decided. An action you have not routed through it is not inspected — and the record says so.
These two are not severable. The headline is bounded only by the subheadline;
published alone it reads as a claim over all agent behaviour. That is the absolute
claim class, which accounts for 37 of the 69 audited rows in the AAASM-5528
inventory — by some distance the most common defect in this product’s published copy.
The rule for downstream pages is: the boundary clause appears on the same screen as
the headline, above the fold, not in a footnote.
Note what the headline deliberately does not say: “your AI agents”. The
possessive quantifies over agents you never routed, which is the wording the table
below bans, and it is the same correction AAASM-5528 applied to “three boundaries for
every agent” → “for a governed agent”. The indefinite article costs nothing and
survives the places non-severability cannot reach — a <title>, an og:title, a
search snippet, a chat unfurl, a slide. Metadata surfaces must carry the bounded
form, because there is no room beside them for a subheadline.
Clause map
Each clause is expressible in ADR 0033 §6’s vocabulary. If a rewrite of the promise cannot be mapped this way, it is not a rendering of the promise — it is a new claim, and it needs its own evidence.
| Clause | ADR 0033 §6 term | Why the term fits |
|---|---|---|
| “decides whether an … action is allowed” | Evaluated | The control plane produces a decision for the action; a decision record exists. |
| “before that action runs” | Denied before execution | Reached where the refusing component sits before the effect — today the proxy, pre-dial, or an SDK shim that honours the answer. |
| “on the paths you route through it” | Unmeasured (by contrast) | Names the boundary. Anything off the path is Unmeasured; the clause exists so the promise does not quantify over agent behaviour. |
| “records what was decided” | Observed | An event attributed to the action. §6 requires this to be durable; emission is best-effort under backpressure, so the clause is bounded by the note at Level 2 step 3 and by its own Provisional row — do not publish it unqualified. |
| “refused” | Denied before execution | As above. |
| “blocked pending a decision” | Approval required | The action is held and a pending approval record exists. §6’s term does not assert that a person can act on it — see the Provisional row. An earlier draft rendered this clause with a human in it; §6 mapped cleanly, and the plain-language wording still added an actor with no shipped surface to act through. A correct §6 mapping is necessary, not sufficient. Tracked as AAASM-5657. |
| “instead of discovered afterwards” | — | A contrast with after-the-fact observability, not a capability claim. Carries no evidence burden. |
Progressive disclosure — four levels
A reader should be able to stop at any level and hold a correct picture. Each level adds precision; none of them retracts what the level above said.
Level 1 — one sentence
The promise, verbatim. Nothing shorter is approved, because everything shorter drops the boundary clause.
Level 2 — three steps
1. Route it. An agent is on a governed path when you have put it there:
launched through aasm run, or started by a developer-tool integration that writes
the proxy settings into the tool’s own configuration, or calling a policy checkpoint
from an SDK. Routing is a thing you do, per agent and per launch. An agent you did
not route is not on the path.
Not every tool has a managed launch to offer. One shipped adapter returns a launch failure rather than a governed command, and another is hard-capped at observation — for those, no proxy environment is injected and there is no data-path mediation to route into. “Supported tool” and “governable tool” are different lists; check the per-adapter boundaries before implying the second.
2. Decide it. Before the action takes effect, something on the path decides whether it may proceed — and which thing depends on the path. The control plane answers policy, budget and approval questions: allow, refuse, or block pending an approval. Budget exhaustion, a suspended agent and an anomaly detection each resolve to a refusal through that same path.
But the control plane is not always the decider, and copy that says it is will be
wrong more often than right. The proxy refuses on its own local configuration for
CONNECT-time egress and for LLM-provider hosts — those code paths contain no gateway
call at all. A gateway Deny stops bytes in exactly one place: an MCP tool-call
envelope on a non-LLM intercepted host with a gateway endpoint configured — and since
llm_only defaults to on, those hosts are not intercepted unless an operator says so.
Attribute the refusal to whichever component actually made it.
Redaction is a separate stage, not a fourth branch of the same answer: it is the
proxy’s outbound credential scan, applied after the connection decision, and it
defaults to redact-and-forward. Do not present allow/refuse/hold/redact as one
four-way per-action verdict — the API’s five-way RuntimeVerdict is a frozen
vocabulary whose derivation is unimplemented and which is surfaced as null, and
presenting it as a live outcome is a forbidden design.
3. Show it. Decisions are written to a hash-chained audit log that you can verify
yourself with aasm audit verify-chain — that command ships in the open-source build.
Where nothing inspected an action, the rule is that the record reports it as
Unmeasured rather than as clean, because an uninspected action must never be
reported as allowed.
State this as a rule, not as finished behaviour. ADR 0033 §4 mandates it, and the transparent-tunnel path implements it — it persists “forwarded, and nothing looked at it”. But §2 records a live defect on the same path: the CONNECT-level decision event still emits an allow for a connection the proxy is about to tunnel uninspected. Until that is fixed, do not write copy that promises the property holds everywhere today.
Emission is best-effort — say so in the same sentence. The gateway advances the chain head before attempting the send, and on a full channel it logs a warning, increments a drop counter and returns the RPC anyway; several sibling emit sites discard the error with no counter at all. So a decision can be made and its record lost, and a dropped entry is indistinguishable from a deleted one. Three bounds belong together wherever this is claimed: which actions are decided (the governed path), whether the record survives (best-effort), and what verification proves (chain integrity, not whether the log is whole). The boundary clause in the promise covers only the first. This is the same rule-plus-open-defect shape as the note above, and the hub’s own security model already states that absence of an entry is not proof of absence.
Why “route it” is step one. The control plane holds no traffic, so a decision only stops something when a component in front of the action blocks on it. That set — components that block on a gateway answer — has exactly two members today: the proxy’s MCP path, and an SDK shim that honours the answer. It is not the set of everything that can refuse: the proxy’s own CONNECT, DLP and LLM-host refusals are local, and they are real. Routing is what puts an action in front of any of them.
Level 3 — for an evaluator
Agent Assembly is a decision point you place in front of an AI agent’s actions, plus the evidence trail that shows what it decided.
What it decides. Which tools an agent may call, which network destinations it may reach, how much it may spend, and which actions are blocked pending an approval. Policy is versioned YAML/JSON you review through normal Git workflows.
Where the decision is applied. In several places, with genuinely different authority. Resist the urge to number them: they are not an ordered chain, one does not cover for another, and an absent one is a reportable state rather than a silent hand-off to the next. That inference — “the SDK did not see it, so the kernel did” — is the specific error the current architecture exists to stop.
- The sidecar proxy is the strongest one. It refuses at CONNECT time, re-checks the host inside the tunnel, blocks or redacts recognised credentials, and adjudicates MCP tool calls — each of those returns before it dials upstream. This is genuine pre-execution refusal, out of the agent’s process.
- The SDK wraps your framework’s tool seam and raises before the wrapped tool body runs. It is deliberately advisory: it is a defence-in-depth posture, not the authoritative gate, and an agent that does not call it is simply not asking.
- Operating-system-level controls are platform-specific and, where they exist today, they mostly observe. On Linux, eBPF probes report TLS plaintext, process execution and file I/O; they do not participate in any allow/deny decision. There is one opt-in syscall guard that terminates a confined process, and it does so asynchronously — the offending syscall runs once before the process dies, so it is Detected, not Denied before execution. macOS has no equivalent adapter, and is simultaneously the only platform where the host-enforcement rung is reachable — through an opt-in, authorized managed-settings write. Both halves are required; writing only the first is the understatement this page bans two sections down. Windows has neither.
What is on by default, and what is not. This is the question that most changes an evaluation:
| Default posture | |
|---|---|
| Approval holds | Off until a policy asks for one, and unresolvable when it does. A hold is produced only by an explicit requires_approval_if expression; three of the eight shipped policy examples declare one, and low-risk.yaml says “No approval gates.” When a hold does fire the gateway blocks awaiting the decision and a timeout resolves to a refusal — genuinely fail-closed — but no shipped operator surface can answer it, so in practice it blocks and then auto-refuses. See the Provisional row before writing anything about human review. |
| Proxy inspection | Narrow. llm_only defaults to on, which TLS-intercepts three built-in LLM hosts. Any other host is tunnelled without payload inspection: the connection is Observed, the payload is Unmeasured. |
| Egress allow/deny lists | Empty. You configure them. The one always-on egress control is the SSRF guard, and it is stronger than a literal check: it refuses IP-literal CONNECT targets and re-checks every resolved address before dialling, so a hostname that resolves into private space is refused too — that is DNS-rebinding cover. Its blocked set spans loopback, private, link-local, broadcast, CGNAT, 0.0.0.0/8, and on IPv6 loopback, ULA, link-local, NAT64, 6to4 and IPv4-compatible. No environment variable can relax it — the config field is hardcoded false with the comment that production binaries can never turn it off. |
| Credential handling | Redact and forward. Blocking on a detected credential is opt-in. Model responses are not scanned. |
| SDK enforcement | Off in the default mode. A policy refusal blocks a wrapped tool only in the check-capable mode; asking for enforcement without it is refused loudly at init rather than silently allowed. |
| eBPF | Off unless deployed. Linux only, needs a privileged loader daemon, and its syscall guard needs an explicit opt-in on top of that. |
| Launching an ungoverned session | Refused. aasm run will not start a tool when no effective policy resolves, and it will not start one whose policy parses but declares no rule — “an absent policy is not permission”, “an empty policy is unconfigured, not allow-all”. Both refuse before anything launches. This is the strongest default-on behaviour in the product and the easiest to leave out of a comparison. |
| The policy engine’s fallthrough | Allow. Once a policy is in force, an action matching no network, tool, capability or approval rule is allowed. Default-open within a policy, default-refuse on having one — state both or the pair is misleading. |
| Budget caps | None unless declared. Limit resolution returns nothing when neither a per-agent nor a global limit is configured, so an undeclared budget means uncapped spend. Five of the eight shipped policy examples declare a daily cap, two of those a monthly one as well, so an evaluator who starts from one usually gets a cap — but a hand-written policy that omits the block has none. |
| Audit | On, best-effort. Hash-chained JSONL, verifiable. Writing is not guaranteed: the chain head advances before the send and a full channel drops the entry, so the log is a record of what got through, not a ledger of what happened. |
What it does not do. It does not govern an agent you did not route. It does not inspect payloads to hosts it is not intercepting. It does not keep a credential out of the agent’s own process — what it does is scan outbound requests on the inspected hosts and remove recognised credentials before forwarding. Its audit chain is tamper-evident, not signed: it is an unkeyed digest chain over the JSONL sink, so anyone able to rewrite that file can recompute it, the database mirror carries no chain, and a dropped entry fails verification the same way tampering does. Nor does a passing verification mean the log is whole — it checks the links between the entries that are there, so a deleted-and-recreated log verifies clean. On Windows there is no local mediation at all.
Level 4 — technical handoff
At this depth, stop paraphrasing and hand the reader the canonical sources. ADR 0033 describes the architecture as six roles — a control plane, managed execution checkpoints, protocol/transport mediation, platform-specific host-level adapters, a credential/capability boundary, and an evidence pipeline. A deployment instantiates some subset of them, an absent role is a reportable state rather than a silent fall-through to another role, and each role’s authority is its own.
| Mechanism | Highest ADR 0033 §6 term it reaches today |
|---|---|
| Proxy — CONNECT, in-tunnel host re-check, credential block, MCP adjudication | Denied before execution, for traffic routed through it and intercepted |
Gateway check_action | Evaluated; reaches Denied before execution only through a caller that blocks on the answer, and today that set is exactly two — the MCP path, plus an SDK shim that honours the answer |
| Runtime policy checkpoint | Evaluated; Denied before execution only if the SDK shim honours the answer |
| Runtime scanner | Redacted — it runs after the action and returns counters, not a verdict |
| SDK client | Evaluated (advisory) |
| eBPF TLS / file / exec probes | Observed / Detected |
| eBPF syscall guard | Detected, plus asynchronous process termination |
| WASM sandbox | Denied before execution, for tools handed to it — but it is not on an agent’s normal tool-call path, so do not cite it as a general guarantee |
Developer-tool config writes (aa-devtool-*) | Not a data-path claim at all. Writing a tool’s own settings file is tool-governance: it takes effect only if the tool honours those keys, and for the macOS managed-settings path whether it does is unmeasured. Any data-path prevention these adapters deliver is the proxy’s, borrowed through the launch environment they inject. This row is the bound on the mechanism Level 2 step 1 introduces — an integration writing proxy settings is not itself an enforcement point. |
Send the reader to ADR 0033 §5.3 for the per-platform matrix and §6 for the vocabulary itself. Do not restate either here; both are snapshots of a specific release and are maintained where they live. One thing §5.3 says that a summariser reliably drops: its macOS row ends “Do not read this as ‘no host enforcement on macOS’.” Carry that sentence with the row, not just the row.
Approved plain-language wording
ADR 0033 §6’s terms are precise but they are engineering vocabulary. Public copy needs plain-language equivalents that mean the same thing to a PM, an SRE and a security reviewer. These are the approved renderings. Use the plain form in body copy; keep the §6 term available wherever a reader might need to verify the claim.
| Concept | §6 term(s) | Approved plain-language wording | Must not shorten to |
|---|---|---|---|
| Governed path (managed path) | — (a scope, not a verdict) | “the paths you route through Agent Assembly”; “an agent you launched under Agent Assembly”; “traffic you send through the proxy” | “your agents”; “your fleet”; “your environment” — all three quantify over things you did not route |
| Pre-execution | Denied before execution | “before the action runs”; “before the request leaves the machine”; “before the tool body executes” | “in real time”; “instantly”; “at runtime” — these describe speed, not ordering, and the ordering is the whole claim |
| Evidence | Observed | “a hash-chained audit record you can verify yourself”; “tamper-evident” | “immutable”; “tamper-proof”; “signed” — the chain is an unkeyed digest, and retention pruning deletes rows. Also avoid treating “verified” as “nothing is missing”: verification checks the links between the entries that are present, so an empty log — or one deleted and recreated — passes and exits 0 |
| Host controls | Observed / Detected (Linux); Unsupported (Windows); macOS is its own case — see below | “operating-system-level controls, where the platform has them”; on Linux, “kernel probes that report activity”; the opt-in guard “terminates a confined process after the fact” | “kernel-level enforcement”; “OS-level protection”; anything implying the Linux mechanism exists on macOS or Windows — and equally, any blanket “no host enforcement on macOS” |
| Managed action | Evaluated | “an action presented for a decision before it takes effect”; “an action that reached a checkpoint” | “any action”; “each action an agent takes” — the second silently re-adds the quantifier the first removed |
| Planned | Planned | “planned — decided, not built yet”, with the ticket reference | “coming soon”; “available in Enterprise”; a roadmap item written in the present tense |
| Not inspected | Unmeasured | “nothing inspected this action, so nothing is known about it” | “allowed”; “clean”; “no issues found” — absence of a finding is a fact about the observer, not the agent |
Two subtleties worth carrying into copy, because both have already produced defects in this repository:
- Unmeasured is scoped to the action, not the connection. A host the proxy does not intercept is still adjudicated at CONNECT by local egress policy, and that connection is recorded. The honest phrasing is “the connection was observed, the payload was not inspected” — not “nothing was observed”.
- An empty audit log is evidence about the observer. It is not evidence that an agent did nothing. Never present a quiet log as a result.
- macOS needs its own sentence, in both directions. It has no kernel-level interception adapter, and saying so is required. But it is also the only platform on which the host-enforcement rung is reachable today, through an opt-in, authorized managed-settings write. Copy that flattens this into “no host enforcement on macOS” is an understatement defect, and it has been fixed once already — do not reintroduce it. State the route; leave the outcome under Provisional.
Rejected wording
Each row was removed from published copy, or considered and refused, for the reason given. The list is short on purpose: it names the patterns that keep recurring, not every sentence ever corrected. The audited per-file record is the AAASM-5528 inventory.
| Rejected | Why | Inventory rows |
|---|---|---|
| “catches everything, including bypass attempts” | The mechanism it described (eBPF) observes. It participates in no allow/deny decision, covers only OpenSSL-linked processes, and its file-I/O probes are x86_64-only. | W1 · W11 · A7 · A17 · A24 · A27 |
| “an action has nowhere to hide” | Asserts the union of the mechanisms is closed. It is not: an unmanaged launch, unrouted traffic, an unhooked TLS stack and an unsupported platform each leave an action outside the boundary. | A8 · A13 |
| “a security checkpoint an AI agent cannot walk around” | Walking around it is a measured bypass — start the agent outside aasm run and no proxy environment is injected. | A2 |
| “no code changes” | True of the agent’s source, false as a prerequisite statement: the tool must be launched so its traffic reaches the proxy and the CA is trusted. Say what is required, not what is not. | D1 · D5 · D8 · A6 · A23 · A26 |
| “immutable audit log” / “signed with HMAC” | The chain is an unkeyed digest over the JSONL sink; retention pruning deletes rows; the database mirror carries no chain. It is verifiable and that is worth saying — but it is tamper-evident, not immutable and not signed. | D9 · D10 |
| “every action” / “every tool call” / “before every agent action” | Quantifies over agent behaviour rather than over what reached a checkpoint. The correct scope is the governed path. | A1 · A3 · A4 · A21 · A37 · D2 · D7 |
| “your whole fleet” / “full fleet” | Coverage is a per-agent, per-launch, per-platform fact. There is no fleet-wide switch. | D1 |
| “universal” / “comprehensive” / “complete” coverage | Each asserts a property no component here provides, and each is unfalsifiable in copy. | forbidden design 7 |
| “cannot be bypassed” / “unbypassable” | Bypass paths are enumerated and published. Claiming otherwise contradicts our own documentation. | A20 · forbidden design 7 |
| “secrets are injected at runtime and never enter the model context” | Advertises a capability a released build cannot reach. What ships is outbound scanning with redaction before forwarding. | W15 · W16 · W17 |
| “kernel-level enforcement” | Attributes the guarantee to the wrong component. The kernel mechanism reports; the proxy refuses. | D11 · W7 · A12 |
| “the SDK denies the action before it runs” | The SDK evaluates and is advisory. Refusal that holds is the proxy’s, out of process. | W18 · A35 · D13 |
| “protects” / “enforces” / “catches”, used without a timing and a posture | ADR 0033 §6 forbids the undifferentiated verb. Each of these can mean observed, detected, evaluated or refused, and the reader cannot tell which. | A36 · §6 |
| “three layers: SDK, proxy, eBPF” as the architecture | A superseded model. An ordered pipeline whose members cover for each other has no way to express an absent member, which is the inference this whole programme exists to stop. | forbidden design 1 |
| A hero that leads with “a governance layer for AI agents” | Accurate but not a user outcome — it names the category, not what changes for the reader. Correct at company altitude; too abstract for the product’s own first screen. | — (new here) |
On the constellation identity
AAASM-4084 introduces Argo Navis as a product alias and visual identity. It is a naming and design layer, and it composes with this brief in one direction only: the metaphor may decorate the promise, it may not stand in for it. A page whose first screen explains a constellation before it explains what happens to an agent’s action has not made the promise; it has deferred it. Where the two compete for the same space, the promise wins.
Altitude — how this relates to the company-level description
Horonomy describes Agent Assembly at company altitude as a governance layer for AI agents — permissions, approval checkpoints, and evidence, which decides which tools, domains, and budgets an agent may use, holds risky actions for human review, and records what happened.
That is correct and this brief does not contradict it. The promise on this page sits one layer below: it keeps the same three ideas — permissions, approval, evidence — and adds the two things a product page must carry that a company page need not, namely when the decision happens (before the action) and where it applies (the paths you route through it). Do not “simplify” a product page back up to company altitude; the boundary clause is what makes it a product claim rather than a category description.
Claim-to-evidence mapping
Every material statement above traces to a merged, re-verified source. E1–E7 are
the evidence blocks in the AAASM-5528 inventory; §n refers to ADR 0033.
| Statement | §6 term | Evidence |
|---|---|---|
| The decision happens before the action runs, where the refusing component sits in front of it | Denied before execution | §2 caller table; §6 mechanism table; E7 |
| The control plane decides but holds no traffic | Evaluated | §2 |
| The proxy refuses before dialling upstream | Denied before execution | E2 (CONNECT 403, in-tunnel host re-check, credential block, MCP adjudication) |
| The SDK is advisory | Evaluated | E3, E7; ADR 0002 |
| A policy refusal blocks a wrapped tool only in the check-capable SDK mode, and asking for enforcement without it is refused at init | Evaluated | Verified directly in the Node SDK’s client construction and init guards, not from E3/E7 — those establish that the wrapper raises before the tool body, which is a different question from whether the default transport can produce a refusal at all. Tracked as AAASM-4991 |
| A hold blocks the check and fails closed on timeout | Approval required | Gateway approval path: the check awaits a decision, and an elapsed timeout yields a Deny fallback. Both OSS gateway bootstraps wire the queue, so this is not a degraded-mode artefact |
| …but the hold has no shipped operator-facing sender | — (a gap, not a claim) | The gateway’s queue is answerable only over the gRPC ApprovalService, and the only clients of it in the tree are two gateway test files. The CLI and dashboard POST to the HTTP API, whose process constructs its own in-memory queue and resolves against that one; there is no gRPC channel and no shared store between the two processes. Verified with a positive control — the equivalent PolicyService client appears in 20+ files including shipped runtime source and a bench |
| Budget exhaustion resolves to a refusal | Evaluated → Denied before execution | Atomic spend reservation inside the same decision path |
llm_only defaults on; three built-in LLM hosts are intercepted | Unmeasured (for other payloads) | E2 |
| Egress allow/deny lists are empty by default; the SSRF guard is not, filters post-resolution, and cannot be disabled by configuration | — | E2 understates this one: it establishes the guard denies unconditionally ahead of both lists, but not the resolved-address re-check or the absence of an opt-out. Both read directly in the proxy’s dial path and config defaults. Understating a shipped control is a defect in the same way overstating one is |
| Credential handling defaults to redact-and-forward | Redacted | E4 |
| The audit chain is an unkeyed digest over the JSONL sink, verifiable in the open-source build | Observed | E5 |
| Emission is best-effort: the chain head advances before the send, a full channel drops the entry and the call returns anyway, and sibling sites discard the error uncounted | Observed, bounded | Read directly in the gateway’s audit-record path and its sibling emit sites — not E5, which covers the chain’s cryptography, not whether an entry reaches it. Open as AAASM-5626 |
verify-chain proves integrity, not that the log is whole — an empty or deleted-and-recreated log verifies clean and exits 0 | Observed, bounded | The verifier’s loop body never runs on zero entries and the terminal return is is_valid: true; the CLI maps that to ExitCode::SUCCESS |
| Kernel probes observe; the syscall guard is opt-in and terminates asynchronously | Observed / Detected | E1, §5.1 |
| Windows has no local mediation | Unsupported | §5.3 |
| An unrouted action is not inspected | Unmeasured | §4 |
Provisional
Not asserted in public copy until the owning work lands. Each is here because it is plausible and unverified, which is exactly the category that produces an overstatement.
| Statement | Why it is provisional | Owner |
|---|---|---|
| “A person can review and release a held action.” Do not write “held for human review”, “approval workflow”, “a reviewer approves it”, or any hero copy implying a human is in the loop | The hold itself is real and fail-closed, but the gateway’s approval queue and the queue the CLI/dashboard resolve against live in different processes with nothing joining them. Until a bridge ships, the truthful account is “blocked pending a decision, which today no operator surface can supply, so it refuses at timeout.” | AAASM-5657 — and it blocks this page, so revisit the promise’s final clause when it closes rather than leaving the narrower wording in place by default |
| “Every decision is recorded.” Do not write “a full audit trail”, “nothing goes unrecorded”, or any phrasing that treats the log as a ledger of what happened rather than of what got through | Emission is best-effort: the chain head advances before the send, a full channel drops the entry and the call returns anyway, and sibling emit sites discard the error uncounted — so a decision can be made and its record lost, indistinguishably from a deletion. Verification does not close the gap, because it checks the links between the entries that are present. Say “records what was decided” with the durability bound attached, never as a claim that nothing is missing | AAASM-5626 |
| Any coverage figure — a percentage, a count of governed actions, a fleet-level number | There is no machine-readable manifest to compute it from, and self-reported layer availability is not evidence of coverage (§7) | AAASM-5531 |
| “Host enforcement on macOS” | ADR 0030’s HostEnforced rung is reachable there — it is the only platform where it is — but it rests on reading back a managed-settings file, and whether the tool honours those keys at runtime is unmeasured. State the route, not the outcome. | AAASM-5526 |
| “eBPF is available to you” as a property of an installed release | The privileged loader daemon that owns every kernel operation is not part of the published release artifacts, and the probe crates build only on a nightly toolchain. Describe eBPF as a Linux mechanism the architecture supports, not as something a reader can switch on today. | AAASM-5526 |
| A named prevented-outcome demonstration (“we stopped X”) | The parent Epic requires a proof that a denied side effect did not execute. Until that harness exists, describe the decision, not the averted consequence. | AAASM-5532, AAASM-5529 |
| Any SaaS availability, region, SLA or compliance commitment | Planned, not available. See Source of truth & status. | The Cloud programme — not AAASM-5579, which is this page’s narrative Epic and owns no SaaS delivery. Route a question here to the maturity map, not to the website backlog |
Using this on a page
For the homepage rewrite (AAASM-5585) and the Product / How It Works rewrite (AAASM-5586), and for any page that makes a product claim:
- Quote the promise, do not paraphrase it. A paraphrase is a new claim. If the layout needs a shorter line, use the headline — and put the subheadline with it.
- Keep the boundary clause above the fold. Not a footnote, not a tooltip, not a “learn more”.
- Pick a §6 term for every verb. If the sentence works with “protects”, “enforces” or “catches”, it is not yet specific enough to publish.
- Name the mechanism that delivers the guarantee. The right guarantee attributed to the wrong component is its own defect class, and it is the one a technical evaluator notices first.
- State the default. A capability that exists but is off is a different product from one that is on. The Level 3 table is the reference.
- Check the direction of the error both ways. Understatement is also a defect — approval holds, pre-dial refusal, the SSRF guard and chain verification all ship, and a page that hedges them away is as wrong as one that overstates.
Last reviewed: 2026-08-06 — AI Agent Assembly Team
Last updated: 2026-08-13 by AI Agent Assembly Team
Risk scenarios — the flagship story and three supporting threats
This page selects the four stories the product’s public surfaces tell: one flagship scenario for the homepage, and three supporting scenarios covering secret exfiltration, a destructive production action, and runaway cost. It exists so that the website, this hub and the demo assets tell the same four stories in the same words, and so that each of those words can be traced to something the product actually does.
It is narrative source material, not a product page. Downstream surfaces (AAASM-5585, AAASM-5589) copy the wording in Approved wording for reuse verbatim rather than paraphrasing it — a paraphrase is a new claim and carries its own evidence burden. That section is split into two tiers: Tier 1 is publishable today; Tier 2 is held until the prevented-outcome harness lands (AAASM-5532, AAASM-5529). Check the tier before shipping a sentence.
What governs this page
This page adds no claims to its sources. Where it appears to, that is a defect in this page.
| Source | What it supplies |
|---|---|
| Product promise & message hierarchy (AAASM-5582) | The one approved promise, the boundary clause, the plain-language wording table, and the Provisional list. Every scenario below is a rendering of that promise applied to one threat. |
| ADR 0033 §6 — claim vocabulary | The eleven terms every “policy decision” and “evidence” field below is written in: Observed, Detected, Evaluated, Denied before execution, Redacted, Approval required, Degraded, Unmeasured, Experimental, Planned, Unsupported. |
AAASM-5527 — capability coverage matrix (verification-reports/AAASM-5527-capability-coverage-matrix.yaml in the core repo) | 80 rows carrying reachability, released_channels, released_platforms, default_state, failure_posture and known_bypasses. Each scenario below names the row IDs it rests on. |
Ticket references are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
Vocabulary precedence is not decided here. These scenarios are written in ADR 0033 §6’s terms because the promise page already is. Where §6’s vocabulary collides with another controlled vocabulary on this hub, that conflict belongs to AAASM-5621, which owns precedence across the hub’s content layers — record it there rather than resolving it on this page. This page follows the same deferral AAASM-5595 makes.
The promise these scenarios render. Agent Assembly decides whether an AI agent’s action is allowed before that action runs — on the paths you route through it — and records what was decided, so a risky call can be refused, or blocked pending a decision, instead of discovered afterwards.
The clause “on the paths you route through it” is load-bearing in every scenario below. None of these stories is a claim about an agent you did not route, a host you are not intercepting, or a platform without the component installed.
How to read a scenario
Each scenario carries eight fields, plus a determination.
| Field | What it answers |
|---|---|
| Threat source | Who or what causes the action. Usually not malice — a plausible instruction, an injected one, or a loop. |
| Requested action | The concrete thing the agent tries to do. |
| Existing-system gap | Why the tools already in place do not stop it. |
| Governed path | What the operator did to put this action in front of a decision. Routing is a thing you do, per agent and per launch. |
| Policy decision | An ADR 0033 §6 term, plus which component actually decided. The right guarantee attributed to the wrong component is its own defect class. |
| Prevented outcome | The specific effect that did not occur — stated as an absence, not as an error. |
| Evidence | An ADR 0033 §6 term, plus the test or record that substantiates it. |
| Known boundary | Defaults, platforms, channels and bypasses. A capability that exists but is off by default is a different product from one that is on. |
Determination is one of:
- Executable today — the mechanism exists, a released binary can reach it, and a standing test on the core repo’s default branch exercises it. The scenario’s defaults and platform boundaries are still stated.
- Executable, default-off — as above, but nothing happens until an operator turns it on. A sentence that is true “once enabled” is false as written.
- Illustrative / planned — the story is useful for explaining the product’s shape but does not correspond to a capability a reader can exercise. It must be labelled wherever it is used.
Every capability cited below was checked against four questions, each of which is invisible to the one before it: is it worded without absolutes; does the guarantee hold in code; is it on by default; and does the named mechanism exist at all and can a released binary reach it. Distribution is per channel and per platform — the core repo publishes through five channels, and absence from the release workflow proves nothing about crates.io.
Scenario status at a glance
| # | Scenario | Mechanism | §6 term reached | Determination | 5527 rows |
|---|---|---|---|---|---|
| F | Flagship — the upload that never happened | aa-proxy CONNECT-time egress refusal | Denied before execution | Executable, default-off (Linux; macOS crates.io only) | N1, N2, L1 |
| T1 | Secret exfiltration | aa-proxy outbound credential scan on inspected LLM hosts | Redacted | Executable today (redact-and-forward is the default, not block) | C1, C6, N3 · C2 default-off · C3 dead code |
| T2 | Destructive production action | Gateway adjudication of an MCP tools/call | Denied before execution | Executable, default-off — HTTP/1.1 POST only; stdio, SSE and Streamable HTTP all outside it | M1, M3 · M5/M6 absent · M7 broken |
| T3 | Runaway cost | Gateway budget reservation | Evaluated (reaches Denied before execution only through a blocking caller) | Executable today, with a silent fail-open | (no positive row — see the scenario) · G9 |
| T3b | Unauthorized payment | — | — | Illustrative — no payment capability exists | — |
Scenarios are lettered F and T1-T3 deliberately. AAASM-5527’s own row IDs use
S for the SDK rows, and this page tells readers to cross-reference those IDs —
an S1 here and an S1 there would collide on exactly the identifier a checking
reader follows, on a page whose whole claim is that none of its scenarios is an SDK
scenario.
Flagship — the upload that never happened
A coding agent tries to send the repository it is working on to an endpoint nobody approved. The connection is refused before it is dialled.
This is the flagship because the denied side effect is objectively testable in the negative: a socket either accepted a connection or it did not, and the check does not depend on trusting anything inside the agent’s process. Note the tense discipline — this section describes a decision and a testable design. The claim that the endpoint never received a byte is Tier 2, and waits on the harness actually being run.
| Field | |
|---|---|
| Threat source | Not necessarily malice. A coding agent reads an issue, a README or a dependency’s docs that contains an instruction to “back up the working tree” to a paste or file-sharing endpoint; or the agent picks a convenient endpoint itself to “share the diff”. The agent is doing what it was told by content it was asked to read. |
| Requested action | An outbound HTTPS connection from the agent’s process to a host outside the approved set, carrying the contents of the working tree. |
| Existing-system gap | The agent’s process has ordinary network access, and nothing in the agent framework distinguishes “fetch the docs page I need” from “POST the repository somewhere”. Code review is after the fact; egress happens at machine speed. Outbound logging tells you it happened — it does not stop it. |
| Governed path | The agent is started through the Claude Code managed launch (aasm run), which writes the proxy settings and the CA into the child process’s environment, so the tool’s outbound connections are dialled through aa-proxy rather than directly (aa-devtool-claude-code/src/lib.rs:356-383). The operator has configured which hosts are approved. Name the tool: of the five shipped adapters this is the only one above Integrated and the only one with a launch evidence test — Copilot’s build_launch_command always returns AdapterError::LaunchFailed (aa-devtool-copilot/src/lib.rs:347), and Codex and Windsurf inject HTTPS_PROXY with no NODE_EXTRA_CA_CERTS and have no launch evidence test. “Supported tool” and “governable tool” are different lists. |
| Policy decision | Denied before execution. The proxy evaluates the CONNECT target and returns 403 before it dials upstream — connect_deny_reason (aa-proxy/src/proxy/mod.rs:1031), called at :1431, returning at :1454 ahead of both the 200 Connection Established at :1459 and any dial. The decider is the proxy’s own local egress configuration, not the gateway — grepping the whole file for gateway/CheckAction/PolicyService/check_action returns zero hits inside connect_deny_reason and zero in the deny block, against 30+ elsewhere in the file as a positive control. Copy that attributes this refusal to the policy engine is wrong. |
| Prevented outcome | No TCP connection to the destination is established, so no byte of the working tree leaves the machine by that route. |
| Evidence | Observed — the CONNECT refusal is recorded as a Blocked decision through emit_rule_refusal (defined aa-proxy/src/proxy/mod.rs:411-423, called for this path at :1440). Not to be confused with the Blocked at :969, which belongs to the credential-DLP refusal inside handle_non_llm_mitm — a different component’s decision. Bounded once, and the bound is default-off — see below. Standing tests: aa-integration-tests/tests/e2e_policy_proxy.rs, cli_proxy_remote_bind_refusal.rs. |
| Known boundary | See below — six parts, all load-bearing. |
Known boundary, in full — six parts, all load-bearing:
- The lists are empty by default.
AA_PROXY_DENIED_HOSTSandAA_PROXY_NETWORK_ALLOWLISTare both empty out of the box (aa-proxy/src/config.rs:75-85), and the row’sdefault_stateis open. This refusal exists because the operator configured it. Never tell this story as something that happens on first run. - The always-on egress control is a different one. The SSRF guard (N2) is on by default, fails closed, cannot be relaxed by configuration, and re-checks every resolved address before dialling — but it blocks private and loopback address space, not an arbitrary public paste endpoint. It does not deliver this scenario; do not credit it with doing so.
- Bypasses are enumerated and published. Unsetting the proxy environment, a client that ignores it, raw TCP that does not speak the proxy protocol (N10), and UDP/QUIC/HTTP/3 (N11) all leave the boundary. The scenario’s scope is B3 — universal within one process, conditional on that process honouring the injected proxy environment. It is not host-wide.
- Channel and platform.
aa-proxyships in the GitHub Release, the Homebrew tap and the install script forlinux_x86_64andlinux_aarch64only — the release workflow states the proxy is a Linux-only component. On macOS the sole route iscargo install aa-proxyfrom crates.io (AAASM-5653). On Windows there is no local mediation at all (P4, Unsupported). A demo recorded on a macOS laptop is running a crates.io build, not a released artifact. - Failure posture is fail-open on this path. If the proxy is not in front of the connection, the connection is simply made.
- The proxy writes no local evidence unless you configure a path. The proxy’s
JSONL sink is built from
AA_PROXY_AUDIT_JSONL_PATH(aa-proxy/src/config.rs:482-483, wired ataa-proxy/src/lib.rs:81); with the variable unset there is no writer and nothing lands on disk, which the crate’s own test pins as the default. The refusal still happens — this is a bound on the evidence, not on the decision — but a demo that promises a record must set the variable, and a page that promises one must say it is configured. Note also that this sink is the proxy’s own; the separate best-effort emission defect tracked as AAASM-5626 is on the gateway’s audit path, and attributing it here would be the wrong component.
Determination: executable, default-off. Rows N1 (reachability: shipped_with_platform_exception, standing evidence), N2, L1 (reachability: shipped, current_level: host_enforced, standing evidence).
Negative control for the flagship (AC 1)
A negative control proves the absence of the effect, not that an error was raised. An agent can receive a 403 and still have reached the endpoint by another route; an agent can raise an exception without ever having tried. Neither an error nor a stack trace is evidence that nothing arrived.
The observable side effect is a TCP connection carrying payload bytes arriving at the destination.
Instrumentation. Stand up a real listener on the address the egress configuration denies. It records, independently of the agent and of the proxy: the count of accepted connections, and the total bytes received. It is the only witness that matters, because it sits on the far side of the boundary — a probe on the near side can observe that its request went out and that nothing obviously failed, and neither fact is evidence.
A precondition the harness must set, and cannot set by environment. A loopback listener is inside the SSRF guard’s blocked set, so the guard — not the egress list — would refuse the connection, and the test would pass for the wrong reason while proving nothing about egress policy. The harness must construct its config with
allow_private_connect_targets = true(aa-proxy/src/config.rs:161, defaultfalseat:184). No environment variable does this;from_envhardcodes it false, which is deliberate — production binaries cannot relax the guard. That makes the loopback-witness form an in-process test, not something a shipped binary can be driven into.
The test and the demo are different artifacts. The in-process test above uses a loopback witness and can therefore assert byte-level absence. A public demo driving a released
aa-proxycannot use a loopback witness at all — it needs a real remote endpoint under the demo’s control, and its absence evidence is that endpoint’s own access log rather than an in-process counter. Do not present one as the other, and do not claim the demo inherits the test’s rigour.
Three runs are required. All three, or the result proves nothing.
-
Positive control — the check can see the effect. With the destination host allow-listed, run the same agent action. Assert
listener.accepted_connections == 1and that the payload bytes arrived. This is what makes a later zero meaningful. Without it, “zero connections” is indistinguishable from a listener that was never listening, a harness that never started the agent, or a test that silently skipped. -
Negative run — the effect is absent. With the destination denied, run the same agent action, then assert in this order:
- First:
listener.accepted_connections == 0andlistener.bytes_received == 0. This is the load-bearing assertion and it must execute before anything that can throw, return early, or short-circuit. - Then: the caller observed a refusal (403).
- Then: a decision record exists attributing the refusal to the proxy’s
CONNECT-time egress check. The harness must set
AA_PROXY_AUDIT_JSONL_PATHfor this to be checkable at all — unset, the proxy writes no local evidence, and the assertion would fail for a reason that has nothing to do with enforcement.
- First:
-
Attempt witness — the agent actually tried. In the negative run, assert that the proxy recorded a CONNECT attempt for that host. Without this, zero connections at the listener is also consistent with an agent that never attempted the upload, which would make the test pass for the wrong reason.
Two honest caveats. It overlaps step 2(c) — a decision record for the refusal already implies an attempt reached the proxy — so treat it as a cheap independent restatement, not a separate discovery. And it witnesses only that a CONNECT was attempted: it can never establish that the agent would have transmitted the payload, because the refusal precedes any body. The positive control is what establishes that the payload would have flowed.
Ordering is the whole point, and it has already gone wrong here — twice. Assert the absence before the error, never after.
State the defect precisely, because the loose version of it is wrong and gets dismissed. In the passing run both assertions execute, so nothing looks broken. The defect appears under falsification: if enforcement is removed or mutated, a test that asserts the error first aborts on that assertion, and the absence check — the one the control exists for — is never reached. The control therefore passes whether or not it can detect the thing it was written to detect, and it fails for the weak reason (“no error was raised”) rather than the load-bearing one (“the effect happened”). It has never been shown to bite.
This is the exact defect corrected across the three SDK negative-control suites under
AAASM-5529 — in each repository one control had the wrong order while its siblings
were already correct. Those corrections sit on unmerged branches at the time of
writing, and AAASM-5529 is still open, so treat this as a known pattern to design
against rather than as a solved problem. The same shape is present today in this
scenario’s own area:
in aa-integration-tests/tests/e2e_policy_proxy.rs, proxy_intercepts_and_enforces_deny
binds a real upstream listener and asserts it is never accepted (:157-162), which is
a genuine negative control — but its 403 assertion at :152 precedes that check. The
other three deny tests in that file have no negative control at all: they target a
hostname with no listener behind it, so there is nothing that could have observed an
arrival. Whoever implements the flagship harness should treat
proxy_intercepts_and_enforces_deny as the starting point and fix the order, not
write a fourth test alongside it.
If the harness uses a construct that asserts a raise around a block, the absence check must sit outside and after that block, not inside it.
What this negative control does not prove. That the upload was impossible — only that it did not happen on this path, in this configuration, on this platform. The bypass list above is the honest scope, and it belongs next to the demo.
T1 — Secret exfiltration
An agent puts a live credential into a request to a model provider. The credential is recognised and removed before the request is forwarded.
Note the verb: Redacted, not Denied. The request proceeds. Copy that turns this scenario into a blocked request is wrong, and the difference is the default.
| Field | |
|---|---|
| Threat source | An injected instruction, or an ordinary “help me debug this” that pastes a config file, an environment dump or a stack trace containing a live key into the model context. |
| Requested action | An outbound HTTPS request to a model provider whose body or headers carry a credential. |
| Existing-system gap | The provider is a legitimate destination, so a host allowlist does not help. The key is already in the process environment or the repository. Once the request lands, the credential is in a third party’s logs, and rotation is the only remedy. |
| Governed path | Traffic routed to the proxy and the proxy’s CA trusted, so the request is inspected rather than tunnelled. |
| Policy decision | Redacted. The aa-security scanner runs in line on the intercepted request and the recognised credential is removed before forwarding (aa-security scanner via intercept_request; the LLM-host path is aa-proxy/src/proxy/mod.rs:1038). Local proxy policy, not a gateway decision. |
| Prevented outcome | The recognised credential does not reach the provider in cleartext. Not “the request was stopped” — it was forwarded with the credential removed. |
| Evidence | Observed — a redaction record naming the fields. Standing test: aa-integration-tests/tests/e2e_secret_interception.rs, whose mod proxy_data_path (:391) terminates TLS at a capturing upstream and asserts on the bytes it actually received: request count 1 (:719), redaction marker present (:725), raw key absent (:729), labelled in-file as a SECURITY INVARIANT. That is a true non-arrival assertion, not merely “a redaction occurred”. Not mod proxy_path (:880), the older scanner-only slice, which terminates no TLS and reaches no upstream. |
| Known boundary | See below. |
Known boundary, in full:
- Redact-and-forward is the default; blocking is opt-in. The default action is
RedactOnly(aa-proxy/src/config.rs:16-27). A third mode,AlertOnly, forwards the credential unmodified and raises no alert — if a page says “alerts you”, it must not mean this mode. - Three hosts, by default.
llm_onlydefaults on, which interceptsapi.openai.com,api.anthropic.comandapi.cohere.com(aa-proxy/src/intercept/detect.rs:31-34). Any other host is tunnelled without payload inspection: the connection is Observed, the payload is Unmeasured. The honest phrasing is “the connection was observed, the payload was not inspected” — never “nothing was observed”. - Recall is bounded by the pattern set, and the bound is wider on the release than
on
main. There is no Stripe detector, and the OpenAI detector keys onsk-while Stripe usessk_. Splitting a secret across a multi-character gap still evades detection (aa-security/src/scanner.rs:3960-4005) — that is true in both places. The single-separator case differs: AAASM-5368 closed it onmain(scanner.rs:1346, implemented at:1395, regression test at:3858), but it is still live in the published rc.6, where AAASM-5368 has zero occurrences. A reader running a released build therefore has a wider hole than a reader of the source. State the pattern-set bound wherever recall is implied, and say which build you mean. - Model responses are not scanned. A credential coming back from the provider is Unmeasured.
- The stronger variant is real, off by default, and unit-evidenced only.
Credential substitution — where the operator’s real provider key is appended at
egress and the agent’s own header stripped, so the real key never enters the agent
(
aa-proxy/src/credentials.rs:198,aa-proxy/src/proxy/http.rs:353,371-383) — is a genuine shipped mechanism. But itsdefault_stateis false (the operator must setAA_PROXY_PROVIDER_KEYS), it covers only the intercepted hosts, and its evidence is unit-only: no end-to-end test proves the substitution reaches upstream. A bounded version of the “never enters the agent” claim is defensible with all of those conditions named, and not otherwise. - Do not tell the credential-injection story. A separate mechanism —
credential injection via
SecretsService.DispatchTool— is dead code (reachability: dead_code, row C3). Copy claiming “secrets are injected at runtime and never enter the model context” advertises a capability no released build can reach, and it is already on the rejected-wording list. Conflating C3 with the substitution mechanism above is exactly what produced that defect. - Channel and platform are the proxy’s, identical to the flagship’s.
Determination: executable today for the redaction path — rows C1
(shipped_with_platform_exception, standing evidence), C6 (detected, standing
evidence), N3 (standing evidence). The substitution variant (C2) is executable,
default-off, unit-evidenced. The injection variant (C3) is not executable — dead
code and must not be told at all.
T2 — Destructive production action
An agent calls a tool that would drop or rewrite production data. The call is evaluated against policy and refused before the proxy forwards it.
As with the flagship, this section describes a decision. “The tool server never received the call” is the prevented-outcome form, and it is Tier 2 — held until the harness has run.
This is the one scenario in the set where the control plane makes the decision that stops the bytes. Everywhere else in this page, the refusal is the proxy’s own local configuration.
| Field | |
|---|---|
| Threat source | An agent asked to “clean up the staging database” that resolves the wrong connection string; or an injected instruction in data the agent was asked to process. The plan reads as reasonable in the transcript. |
| Requested action | An MCP tools/call carrying a destructive operation — a DROP TABLE, a migration that rewrites rows, a delete against a production namespace. |
| Existing-system gap | Tool servers execute what they are asked. Nothing sits between the agent’s intent and the tool server, and the destructive step is indistinguishable in shape from the routine ones the agent is supposed to perform. |
| Governed path | Traffic routed to the proxy, CA trusted, a gateway endpoint configured, and the MCP host intercepted as a non-LLM host. Both CLI routes — aasm proxy start --gateway <url> (aa-cli/src/commands/proxy/start.rs:129-133) and an aa-runtime-spawned proxy (aa-runtime/src/runtime.rs:257-258) — force AA_PROXY_LLM_ONLY=false, widening interception to every host on the machine. The mechanism does not require that: see the boundary. |
| Policy decision | Denied before execution, and the decider is the gateway: evaluate_mcp_request (aa-proxy/src/proxy/mod.rs:614, invoked at :834) calls aa-gateway PolicyService.CheckAction. This is the only gateway-bound pre-dial block in the system. |
| Prevented outcome | The tools/call envelope is not forwarded. The MCP server never receives the call, so the table is not dropped. |
| Evidence | Observed — a decision record. Standing tests: aa-integration-tests/tests/e2e_mcp_interceptor.rs, e2e_mcp_redact.rs. Malformed and batched envelopes carrying tools/call are adjudicated too (row M3) — but that row’s evidence is unit-only, and it has a live bypass; see the boundary. |
| Known boundary | See below — the transport bound is the one that matters most. |
Known boundary, in full:
-
One transport is covered, and it is not the one people assume. M1’s transport is plain HTTP/1.1
POSTwith an explicitContent-Length. Every other MCP transport is outside it, and M1’s ownknown_bypassessays so by listing “M2 through M9”:- stdio (subprocess pipes) —
absent_mechanism(M5). This is the most common MCP transport in practice, and it is entirely unmediated. The product models stdio servers (aa-core/src/dev_tool.rs:112-121) and cannot mediate them. - SSE (
text/event-stream) —absent_mechanism(M6); the SSE leg is raw-copied unscanned. - Streamable HTTP —
coverage: unmeasured, and worse than uncovered (M7). Itsfailure_postureissilent_truncation: the client receives an empty 200.aa-proxy/src/proxy/http.rs:13claims the MCP path falls back to a transparent relay; it does not. Never advertise this transport as governed — an earlier draft of this page did exactly that, which is the inversion this bullet exists to prevent. - WebSocket — Unsupported (M8).
- An MCP endpoint on a built-in LLM host is DLP-scanned but never
adjudicated (M9,
coverage: redacted).
- stdio (subprocess pipes) —
-
Off by default, and the shipped CLI routes couple it to whole-machine interception — but the mechanism does not. The row’s
default_stateis false. Both CLI routes widen TLS interception to every host on the machine, which is a material operational consequence and not a footnote. The capability itself is narrower than that, and understating it is its own defect:should_mitm(aa-proxy/src/proxy/mod.rs:1385-1388) unionsmitm_hosts, soAA_PROXY_GATEWAY_ENDPOINTtogether withAA_PROXY_MITM_HOSTS, leavingllm_onlyon, adjudicates exactly the hosts you name. What is missing is a CLI flag that produces that configuration — ergonomics and documentation, not capability. Do not tell an evaluator they must intercept the whole machine to get MCP adjudication.A source conflict, recorded rather than silently resolved. AAASM-5527’s two halves disagree here. The YAML’s M1
notesstill says “the only supported route … forcesAA_PROXY_LLM_ONLY=false”; the threat-model MD carries a bolded “Correction to an earlier revision, which called this ‘the only supported route’. It is not.” and finding F7 spells out the targeted alternative. This page follows the MD. The divergence is worth closing at the source. -
Only
tools/call. Every other MCP method is Unmeasured (row M4). -
A hold cannot be reached here. A gateway
Pendingdecision is downgraded toDenyinside the tunnel (mcp_enforce.rs:135-144), so this path cannot produce “blocked pending a human decision”. Separately, and more broadly, no shipped operator surface can answer a hold at all — see the Provisional row in Product promise (AAASM-5657). Do not attach an approval narrative to this scenario. -
Per-agent MCP policy and per-agent MCP audit do not exist and must not be claimed (AAASM-5533). The decision is not attributed to an individual agent on this path.
-
The batch/malformed-envelope defence (M3) is unit-evidenced and has an open bypass. Its
evidence_qualityisunit_only— the wire-level teste2e_mcp_interceptor.rshas no batch case — andmentions_tools_callinspects only one level (aa-proxy/src/intercept/mcp.rs:128-130), so a nested batch, or one whose elements carryparamswithout a literal top-levelmethod, is not detected. For a fix to a bypass ticket (AAASM-4070) that coverage is thin; a wire-level negative control is owned by AAASM-5532. Cite M3 as a defence that exists, not as one that is wire-proven. -
Channel and platform are the proxy’s, identical to the flagship’s.
Determination: executable, default-off, over HTTP/1.1 POST with an explicit
Content-Length on Linux — rows M1 and M3 (shipped_with_platform_exception,
standing evidence). Not executable over stdio or SSE (M5, M6:
absent_mechanism), and not over Streamable HTTP (M7: unmeasured, and
functionally broken). A demo or a page that shows a local stdio MCP server being
governed this way would be showing something that does not happen.
T3 — Runaway cost
An agent enters a retry loop, or fans out across a large repository, and keeps spending. The call that would cross the declared cap is refused by the policy decision.
Two disciplines apply here at once. “Refused rather than billed” is the prevented-outcome form and is Tier 2. And even the decision only stops the call where something in front of it blocks on the answer — which, for a model call, is not the default. See the boundary.
| Field | |
|---|---|
| Threat source | No adversary at all. A retry loop, a recursive plan, or a fan-out over more files than anyone estimated. |
| Requested action | The next model call, after the team’s declared spend cap has been reached. |
| Existing-system gap | Provider dashboards settle hours to a day late, and none of them refuse the next call. By the time the number is visible the spend has happened. |
| Governed path | An action evaluated by the gateway, under a policy that declares a budget. |
| Policy decision | Evaluated. Spend is reserved atomically inside the same decision path, serialised per tenant (aa-gateway/src/budget/tracker.rs:126; the reservation itself is tracker.rs:859, AAASM-4124), and the over-cap case resolves to BudgetStatus::LimitExceeded (tracker.rs:33, :617, :643, :662). It reaches Denied before execution only through a caller that blocks on the answer — and for a model call neither blocking caller is in the path by default. See the boundary. |
| Prevented outcome | Conditional, and this is the honest form: the decision to refuse is produced. Whether the call is stopped depends on a component in front of it blocking on that decision. Without one, the refusal is recorded and the call still goes out. |
| Evidence | Observed — a decision record. Standing tests: aa-gateway/tests/policy_service_test.rs:245, which drives check_action over the wire and asserts Decision::Deny with the budget reason, and :339, which asserts Decision::Deny only; plus the engine unit test budget_denies_when_exceeded (aa-gateway/src/engine/mod.rs:4167). Not e2e_budget.rs — see the boundary below. |
| Known boundary | See below — including an evidence correction and a matrix gap this page must not paper over. |
Known boundary, in full:
-
No cap unless you declare one. Limit resolution returns nothing when neither a per-agent nor a global limit is configured, so an undeclared budget is uncapped spend. Most shipped policy examples declare a daily cap, so an evaluator who starts from one usually gets a cap — but a hand-written policy that omits the block has none.
-
A corrupt or unreadable budget store fails open, silently, and resets the cap to zero spend. The gateway falls back on a load failure (
aa-gateway/src/server.rs:260-268), and a write failure prints to stderr and continues (aa-gateway/src/budget/persistence.rs:85-86). Row G9’sfailure_postureisfail_open_silent. This is the sharpest boundary in this page: the control is real, and the mode in which it stops working produces no signal on the decision path — it does emit atracing::warn!(“failed to load budget state, starting fresh”), so an operator watching logs can see it; nothing downstream of the decision can. Any page that claims a spend guarantee must carry it. -
A refusal only stops something through a caller that blocks on the answer, and for a model call there is no such caller by default. The gateway holds no traffic. Budget exhaustion resolves to a refusal in the decision; whether that refusal prevents the call depends on the same two blocking callers as every other gateway decision — and neither covers this scenario out of the box:
- The MCP path adjudicates
tools/callon a non-LLM intercepted host. An MCP endpoint on a built-in LLM host is DLP-scanned but never adjudicated (M9), so an ordinary model call is not on it. - The SDK is advisory, and the Node default routes every check through an allow-all no-op (S7, AAASM-4991); the documented Python quick-start installs no interceptor at all (AAASM-5661).
So by default this scenario yields Evaluated — a refusal decided and recorded — not a call prevented. Say “the cap was reached and the call was refused” only where one of those callers is genuinely in the path, and say which.
- The MCP path adjudicates
-
The obvious test does not prove this scenario.
aa-integration-tests/tests/e2e_budget.rslooks like the evidence and is not: all of its tests driverecord_raw_spenddirectly and assert on the returnedBudgetStatus, never callingcheck_actionor the policy engine — its own header records the substitution at:15.record_raw_spendaccounts for spend; it is not an authorization gate. Citing it for pre-execution denial would be a wrong-reason pass, which is why the Evidence field above names the gateway tests instead. -
A matrix gap, stated rather than hidden. AAASM-5527 assigns no positive capability row to budget enforcement — the only budget row in the matrix is G9, the failure mode, whose own
evidencefield is markedGAP. So this scenario’s determination does not rest on a matrix row the way the other three do; it rests on the call path and the gateway tests cited above. That is different provenance, and a reader is entitled to know which of these four scenarios has it.
Determination: executable today, with the silent fail-open stated alongside it.
The call path is check_action (aa-gateway/src/service/policy_service.rs:1599) →
evaluate → Stage 7’s budget check (aa-gateway/src/engine/mod.rs:1687-1691) →
EvaluationResult::deny_with, with a TOCTOU-safe reservation for LLM spend rewriting
the response to a hard Deny (policy_service.rs:1341, called at :1669). Derived
from that path and its gateway tests, not from a 5527 capability row, because
none exists.
T3b — Unauthorized payment: illustrative only
The parent scope pairs runaway cost with “unauthorized payment”. These do not have the same answer, and they must not be told as one story.
There is no payment, purchase, checkout or spend-authorization capability in the
product. The argument is structural, not a grep: the policy engine’s action type
GovernanceAction (aa-core/src/policy.rs:194) has exactly six variants —
ToolCall, ToolResult, FileAccess, NetworkRequest, ProcessExec,
SendMessage. None is a payment, and nothing can be evaluated that is not one of
them. That cannot be falsified by a better search.
Expect to find payment vocabulary anyway, and do not mistake it for capability.
process_refund appears roughly 45 times across 25-plus files — as a tool name in
dashboard E2E fixtures and as an approval action in aa-cli approval-client tests —
and a policy-YAML documentation example even grants it with limit_per_hour and
requires_approval_if: "amount > 100". There are checkout-agent and refund-agent
identifiers in aa-api route tests, and a v1 wireframe sketches an approval queue
containing “refund $500 via stripe”. All of it is fixture, test or design material:
demo data shaped like a capability, not a capability. An earlier draft of this page
claimed a sweep “returns only three unrelated classes”, which was simply incomplete —
the determination survived, the methodology boast did not.
A methodology note, because it invalidates negatives elsewhere too.
git grep -Edoes not support\b. A sweep written as\bcharge|\bspendmatches nothing and looks exactly like a real absence. Any negative finding derived from a\bpattern needs re-deriving with a positive control in the same command.
The nearest real controls are generic, and none of them knows what a payment is:
- refusing the connection to a payment API host — the flagship’s mechanism, and it treats that host like any other unapproved host;
- refusing an MCP
tools/callthat happens to be a payment tool — T2’s mechanism, with T2’s transport bound; - the spend cap above, which counts model tokens, not money moved.
Determination: illustrative. If a surface needs a payment story to make the category legible, it must be labelled illustrative in place, and it must not be demonstrated as a product capability. In particular it must not be told as “held for human review” — that is a Provisional claim on the promise page, blocked on AAASM-5657, because no shipped operator surface can answer a hold.
Approved wording for reuse
These are the sentences downstream surfaces may use verbatim. They come in two tiers, and the split is not stylistic — it is a publication gate.
Why there is a gate.
product-promise.md’s Provisional list defers “a named prevented-outcome demonstration” — any “we stopped X” — until the proof harness lands under AAASM-5532 and AAASM-5529. Both are open; this page says so itself. So a sentence asserting that an endpoint never received a byte, or that a call was refused rather than billed, is a claim this product cannot yet substantiate, however well-designed the negative control on this page is. Designing the control is not the same as having run it.Tier 1 is publishable today. Tier 2 becomes publishable when those two tickets close and the harness has actually run — not before.
The gate binds the demo lane too. AAASM-5589 embeds a denied-action proof into the website, which is the prevented-outcome demonstration in its purest form — so it is more exposed to this gate than the homepage is, not less. A demo that shows an averted consequence is making the Tier 2 claim in the strongest available medium.
Three constraints on Tier 1, all inherited from product-promise.md:
- Tier 1 sentences are not severable from the flagship boundary clause. The clause appears on the same screen, above the fold — not a footnote, not a tooltip, not a “learn more”. A Tier 1 sentence published alone reads as a claim over all agent behaviour, which is the single most common defect in this product’s published copy.
- No scenario sentence on this page is approved for a metadata surface. A
<title>, anog:title, a search snippet, a chat unfurl or a slide has no room for a boundary clause beside it — and constraint 3 forbids shortening one to fit. Useproduct-promise.md’s headline, which is written to survive exactly those places: “Decide what an AI agent may do — before it does it.” Its indefinite article is deliberate — “your AI agents” would quantify over agents nobody routed. - Quote, do not paraphrase. A paraphrase is a new claim and carries its own evidence burden. If a layout needs something shorter than what is here, that is a layout problem, not a wording problem.
Tier 1 — publishable now (decision-scoped)
Each of these describes what was decided, which is what the product can evidence today. None asserts an averted consequence.
Flagship, long form (for a homepage section):
A coding agent decided to upload the repository it was working on to an endpoint nobody had approved. Because the agent was launched through Agent Assembly’s managed launch for Claude Code, the connection was evaluated against the destination list the team configured, and refused before the proxy dialled it.
Flagship, short form (for a hero panel or a card):
The connection was refused before it was dialled.
Flagship, boundary clause — required on the same screen, above the fold:
This applies to connections you route through Agent Assembly — today, via the managed launch for Claude Code — on a host where the proxy is installed: a released artifact on Linux, and on macOS via
cargo install aa-proxy. On Windows there is no local mediation. It applies against an approved-destination list you configure. An agent you did not route is not inspected, and the record says so. A durable local record of the refusal exists only where the proxy’s audit path is configured.
T1 — secret exfiltration:
An agent pasted a live API key into a request to its model provider. On the provider hosts Agent Assembly inspects, the key was recognised and removed before the request was forwarded. Detection is bounded by the patterns it knows, and the default is to redact and forward, not to block.
T2 — destructive production action:
An agent called a tool that would have dropped a production table. The call was evaluated against your policy and refused before the proxy forwarded it. This covers MCP tool calls sent as ordinary HTTP POSTs; tool servers you run over stdio (the most common setup), SSE, or Streamable HTTP are not on this path.
T3 — runaway cost:
An agent in a retry loop reached the spend cap its team had declared, and the next call was refused by the policy decision. A cap exists only where a policy declares one, and the refusal stops the call only where something in front of it waits for that answer.
Tier 2 — gated until AAASM-5532 and AAASM-5529 close
Do not publish these yet. They are recorded here so that the wording is settled in advance and nobody re-derives it under deadline once the gate lifts. Each states a prevented outcome, which is precisely what the harness must first demonstrate.
(Flagship) The endpoint never received a byte.
(T2) The tool server never received the call.
(T3) The call was refused rather than billed.
When the gate lifts, these attach to the Tier 1 sentences; they do not replace the boundary clause.
The four verbs these scenarios are allowed to use, and nothing vaguer: refused before it ran (Denied before execution), removed before it was forwarded (Redacted), evaluated against your policy (Evaluated), recorded (Observed). If a sentence works with “protects”, “enforces” or “catches”, it is not specific enough to publish.
What these scenarios must not be used to say
- Not “Agent Assembly stops your agents from leaking data.” Each scenario is scoped to one process, one routed path, one configured rule and one platform.
- Not “nothing gets past it”, “catches everything”, or “an agent cannot walk around it”. The bypasses are enumerated in each scenario and are published.
- Not a count, a percentage or any coverage figure derived from these four stories. There is no machine-readable manifest to compute one from, and self-reported layer availability is not evidence of coverage.
- Not “held for human review” on any of them. A hold is real and fails closed, but no shipped operator surface can answer it (AAASM-5657).
- Not an audit claim stronger than tamper-evident. The chain is an unkeyed digest, emission is best-effort, and a passing verification does not mean the log is whole.
- Not a story told on a macOS or Windows screenshot without its platform sentence. The proxy that delivers three of these four scenarios is a Linux release artifact; on macOS it is a crates.io install, and on Windows there is no local mediation.
- Not the credential-injection framing for T1. That mechanism is dead code.
- Not T2 over a stdio MCP server (no interception mechanism), and not over Streamable HTTP, which the matrix records as functionally broken rather than merely uncovered.
How this page meets its acceptance criteria
| Criterion | How it is met |
|---|---|
| The flagship can be demonstrated with a negative control proving the denied side effect did not happen | Negative control for the flagship specifies the observable side effect (accepted connections and bytes at an independent listener), the absence check, the paired positive control that proves the check can see the effect, an attempt witness, the assertion ordering (absence first, error second), the allow_private_connect_targets precondition that no environment variable can supply, and the split between the in-process test and a public demo, which cannot use a loopback witness. Designed here; running it is AAASM-5532 / AAASM-5529, which is why the prevented-outcome wording is gated. |
| Scenarios do not imply host-wide or cross-platform coverage when only a managed path is exercised | Every scenario carries its boundary_class, its routing precondition, its default state and its per-channel, per-platform release position. The flagship is stated as B3 — universal within one process, and the Windows and macOS positions are stated in each. |
| The story is understandable without SDK/eBPF knowledge | No scenario in the set is an SDK scenario, and none relies on eBPF. Each is told as an action, a decision and an outcome. The mechanism names appear only in the boundary and evidence fields, where a technical reader needs them to verify. |
| Scenario wording is approved for reuse by website, Docs Hub and demo assets | Approved wording for reuse gives the verbatim sentences in long, short and boundary forms, with the required co-location rule for the flagship’s boundary clause — split into Tier 1, approved for use now, and Tier 2, settled in wording but gated on AAASM-5532 / AAASM-5529 so that a prevented-outcome claim is not published ahead of the proof product-promise.md requires for it. |
Last reviewed: 2026-08-06 — AI Agent Assembly Team
Last updated: 2026-08-06 by AI Agent Assembly Team
Security model
AI Agent Assembly is a governance layer for AI agents — it enforces policy, tracks cost, and intercepts unsafe actions before they run. This page documents the security posture behind that enforcement, for enterprise security and compliance teams. It covers the layered defense model, a STRIDE threat analysis, the cryptography in use, and the audit and compliance posture.
IronClaw five-layer defense
AI Agent Assembly groups its security controls into five named layers. Each layer is independently deployable and adds defense-in-depth. They are not a fallback chain: a layer you do not deploy is absent, not covered by another, and each row below states its own bound.
| Layer | Name | What it does |
|---|---|---|
| 1 | Boundary | Network perimeter: the sidecar proxy (aa-proxy) can enforce an egress allowlist on the traffic it intercepts, but that allowlist is empty by default — default-open, so out of the box the only host-level control that always applies is the SSRF guard, which refuses CONNECT targets resolving to loopback, private, link-local or cloud-metadata addresses and cannot be relaxed in a production binary. eBPF sensor (aa-ebpf, Linux 5.8+ with BTF) is mostly observation: uprobes attach SSL_read/SSL_write on the first mapping whose path contains libssl.so, so they miss OpenSSL 3.x _ex callers and are blind to any stack that neither exports those symbols nor loads as a libssl.so mapping — Go, rustls, GnuTLS, NSS, and statically-linked BoringSSL as in Node. One probe can enforce — a syscall allowlist that SIGKILLs a monitored process — but it is opt-in and disabled by default, planned only when a confine-target PID is explicitly configured and the policy lowers to a non-empty allowlist; and even then the signal lands after the offending syscall has executed. Observing a syscall is not preventing it |
| 2 | Identity | Agent and user authentication: the gRPC agent plane is authenticated in the handlers, not uniformly at the interceptor — registration is gated by a did:key binding, a single-use identity-bound nonce and an Ed25519 possession proof; subsequent calls carry a random per-agent credential token (UUID, constant-time compare, no expiry). See Authentication flow for the one gap this leaves. Operator SSO (SAML 2.0 / OIDC) is not implemented — see Authentication flow. A separate HMAC-SHA256 JWT (24h TTL) protects the REST/admin surface only, and that surface’s auth is off by default — see the callout in Authentication flow below |
| 3 | Policy | Runtime governance: YAML/JSON policy rules evaluated by the gateway policy engine — for the calls that reach it, which is not every agent action. SDK-instrumented calls and the proxy’s non-LLM MitM path consult the gateway. The proxy’s LLM path does not: it applies a local in-tunnel egress allowlist and returns 403 itself, without a gateway round-trip — and that allowlist is empty by default, so unless an operator configures one it denies nothing on that basis and only the always-on SSRF guard applies. Under the llm_only default, hosts the proxy does not intercept are transparently tunnelled and evaluated by nothing |
| 4 | Vault | 🗺️ Largely aspirational. An in-memory SecretsStore is mounted, but it is empty in every shipped build with no route or command able to populate it, there is no encryption at rest or key management, and successful resolution hands the plaintext back to the caller — see Secrets management. Ed25519 is used for the one-time agent registration proof, not for a vault |
| 5 | Telemetry | Audit and observability: a JSONL event log with an unkeyed SHA-256 hash chain (verify with aasm audit verify-chain), append-only by convention and best-effort on emission. A shipped gateway writes one fixed file, not per-session files — see Audit log for the exact bounds; Slack/webhook connectors for alerting on policy violations |
How the five layers relate to the interception mechanisms. The five defense-in-depth layers above (Boundary, Identity, Policy, Vault, Telemetry) describe what is protected. The interception mechanisms named on the landing page and marketing site — the SDK layer, the sidecar proxy (
aa-proxy), and the eBPF sensor (aa-ebpf) — describe where a decision is applied, and each sits inside the Boundary layer. They are two views of one system, not two competing models. They are not an ordered pipeline and not a ranking: each is deployed on its own and reaches its own claim level, and an absent one is reported as absent rather than covered by another. Where this page and ADR 0033’s six-element model differ, ADR 0033 wins.
STRIDE threat model
The table below maps each STRIDE category to the five primary components of AI Agent Assembly and the control that mitigates it.
| Component | Spoofing | Tampering | Repudiation | Info Disclosure | Denial of Service | Elevation of Privilege |
|---|---|---|---|---|---|---|
| Language SDK | One-time Ed25519 possession-proof at registration, then a random per-agent credential token (constant-time compare) on every call | Package-manager checksums (Cargo/npm/PyPI) protect the download. That is the registry’s integrity control, not one this product implements — nothing here verifies the SDK at runtime | Calls are logged with agent ID and timestamp on a best-effort path — entries are dropped under backpressure, so absence is not proof of absence (Audit log) | gRPC transport is plaintext by default — the app-layer credential-token interceptor authenticates every call; mTLS is an optional, unwired hardening layer; a redaction scanner runs over logged fields and audit payloads to strip credential-shaped values — a mitigation with finite detection coverage, not a guarantee that a secret can never be logged | Budget caps are absent unless a policy declares one — enforcement is limit-gated and the tracker starts with no team limit, so an undeclared budget means uncapped spend, and no shipped policy example sets one. Budgets cap spend, not request rate | Policy engine enforces agent scope; no ambient privilege |
| Gateway (aa-gateway) | Credential-token interceptor is fail-closed on audit, approval, topology, secrets and invalidation. PolicyService and AgentLifecycleService are enrichment-only at the interceptor, but their handlers self-validate: registration requires a did:key binding plus a single-use nonce plus an Ed25519 possession proof, and CheckAction rejects a missing or mismatched token for a registered agent. Gap: an unregistered caller with an empty token still gets an evaluation (see Info Disclosure). REST/admin surface can opt into JWT validation, off by default | Per-service decoded-message-size caps; policy documents are schema-validated and reject unknown keys fail-closed. This is not blanket input validation on every RPC | JSONL audit log with an unkeyed SHA-256 hash chain (aasm audit verify-chain); the DB mirror carries no chain metadata, emission is best-effort, and budget debits are not separately audited — repudiation cover is partial, see Audit log | “Internal-only” is an operator responsibility, not something the software enforces. It binds 127.0.0.1:50051 by default, but --listen will bind it anywhere and the transport is plaintext (mTLS is unwired). PolicyService and AgentLifecycleService are mounted with an enrichment interceptor that returns Ok unconditionally, so the interceptor does not gate them — their handlers do: registration enforces a did:key binding, a single-use nonce and a possession proof, and CheckAction rejects a missing or mismatched credential token for a registered agent. The residual gap is narrow but real: an unregistered caller with an empty token can obtain a policy evaluation, treated as anonymous. Audit, approval, topology, secrets and invalidation get the fail-closed interceptor | Per-team budget caps block runaway spend only where a policy declares a limit — the same default-open shape as the egress allowlist two cells left. No shipped policy example sets a team limit | RBAC on administrative endpoints only when auth is enabled — the gateway’s REST/admin surface is bypass-by-default, and under AuthMode::Off every guarded route resolves to a synthetic admin caller, so no role check applies |
| Sidecar Proxy (aa-proxy) | The proxy mints a per-host certificate from its own CA, which the agent must trust — so a third party cannot impersonate the proxy to an agent that has the CA installed. It does not stop an agent choosing not to route through the proxy at all | On connections the proxy itself dials — i.e. hosts it intercepts — upstream certificates are validated against the OS root store. Two bounds: skip_upstream_tls_verify replaces that with an accept-any verifier (integration tests only), and hosts transparently tunnelled under the llm_only default are never terminated by the proxy, so it validates nothing on them | 🗺️ No proxy audit file is produced by any shipped build. A ProxyAuditEntry JSONL stream exists in the code, but ProxyServer::new hardcodes the sink to None, the emit path early-returns, and ProxyConfig has no audit-path setting — so no operator configuration turns it on; reaching it requires embedding aa-proxy as a library. Were it enabled it would be a separate stream from the gateway’s, not hash-chained, losing a line on write failure. Do not plan an audit trail around it | Proxy does not log request/response bodies by default. The credential scanner is on by default but in RedactOnly mode — it redacts and forwards rather than blocking — and under the llm_only default only built-in LLM hosts are intercepted, so only those are scanned at all | ⚠️ Nothing bounds connection concurrency. There is no connection pool, no semaphore, no concurrency limit and no circuit breaker anywhere in aa-proxy; the accept loop spawns an unbounded task per connection. The only bounded resource is the TLS certificate LRU cache | On Linux the process sets PR_SET_DUMPABLE=0 (best-effort, non-fatal) so same-uid processes cannot ptrace it or read a core dump. Running as an unprivileged user and restricting filesystem writes are deployment responsibilities — the software does not enforce either |
| eBPF Sensor (aa-ebpf) | ⚠️ Loaded only by a privileged loader daemon (aa-ebpf-loaderd). The daemon is absent from every packaged release channel — the GitHub Release assets, the Homebrew tap, Docker/GHCR images and the curl installer all omit it. It is published to crates.io as part of the aa-ebpf crate, an undocumented install path on Linux. Building it there additionally requires a nightly toolchain with rust-src and bpf-linker; without them the build produces empty stubs that refuse to load, failing closed. Without the daemon socket the runtime silently degrades to SDK-only interception rather than reporting an error | BPF verifier rejects unsafe programs at load time | Kernel event timestamps come from a monotonic clock, so they cannot be reordered by adjusting wall-clock time; this says nothing about altering a record after it is written | ⚠️ Reads considerably more than TLS buffers, including full file paths. Alongside the SSL_read/SSL_write uprobes: fourteen file-I/O kprobe/kretprobe targets (openat, read, write, unlinkat, renameat2 and their legacy entry points) carry a 256-byte path field, so file paths are captured; exec tracepoints capture pid, ppid, uid and filename (PID-filtered — an empty filter map emits nothing); fork/clone is traced for descendant tracking; process-exit events are emitted; and a syscall tracepoint sees syscall numbers. TLS, file-I/O and exec probes all load by default. Unlike exec and the syscall guard, the file-I/O kprobes have no fork propagation. Treat this as the collection scope for a privacy assessment | eBPF programs have bounded execution; the verifier rejects a program with an unbounded loop | The daemon runs under a systemd unit whose AmbientCapabilities / CapabilityBoundingSet grant CAP_BPF and CAP_PERFMON — and it retains them for its lifetime, because it exists to service further load and map-update requests over its control socket. Nothing drops a capability after load. The privilege separation that does hold is structural: the bounding set is limited to those two, the unit sets NoNewPrivileges and ProtectSystem=strict, the daemon owns every aya::Ebpf handle, and aa-runtime reaches it only over a root-owned 0600 socket with no raw fd or handle crossing the boundary |
| REST API (aa-api) | API-key or JWT validation on every request; aa-api defaults auth on, the local in-memory development mode bypasses it. No SSO | Malformed input is rejected by serde deserialisation into typed extractors, not by runtime schema validation — there is no JSON-Schema or OpenAPI validation middleware. utoipa generates the spec and CI checks it for drift, but schema-only constraints (minLength, pattern, enum on a String, maxItems) are enforced only where a handler hand-wrote a check | Mutating API calls are logged with actor identity on the same best-effort path; under AuthMode::Off the recorded actor is the synthetic admin caller, not a real identity | 🗺️ HTTPS and HSTS are not provided by this software. It serves HTTP; terminating TLS and setting HSTS is your deployment’s job. Query strings are not logged at all: the request-tracing span records method, path and request id, and .path() excludes the query. (Credential redaction over request targets is the proxy’s, not this component’s, and is a no-op when its scanner is disabled) | Per-key rate limiting is enforced in aa-auth. 🗺️ There is no DDoS mitigation and no load balancer — the previous claim described infrastructure that is not deployed | 🗺️ Not enforced at the API layer. The router gate verifies an API key or JWT; per-handler scope and tenant checks are explicitly the handler’s responsibility, so this is not a systematic cross-tenant control |
Traceability: Each STRIDE row maps to a specific IronClaw layer control. For configuration paths and runbook references, consult the security runbook in the
agent-assemblyrepository.
Cryptographic primitives
| Primitive | Algorithm | Key length | Usage | Rotation cadence (NIST SP 800-57) |
|---|---|---|---|---|
| Agent registration proof | Ed25519 | 256-bit | One-time possession-proof signature over a server-issued nonce, verified at RegisterAgent; not a reusable bearer credential | Agent-supplied keypair; not gateway-managed |
| Agent credential token | UUID v4 (CSPRNG) | 122-bit random | Bearer credential presented on every agent-plane gRPC call after registration; validated with a constant-time compare | No expiry — replaced only on re-registration |
| REST/admin session token | JWT (HMAC-SHA256) | 256-bit | Authenticates REST/admin API callers; only issued when gateway auth is explicitly enabled (off by default) | 24h token TTL |
| — | — | Removed. No AES-256-GCM implementation exists in the workspace crates — see Secrets management | — | |
| Webhook signature verification (inbound) | HMAC-SHA256 | 256-bit | Verifies the signature on audit webhooks received from SaaS coding-agent providers (Claude.ai, ChatGPT, Cursor), checked before the body is parsed. There is no outbound webhook signing path — this direction is verification only | Shared secret is issued and rotated by the sending provider, not by this software |
| TLS (transport) | TLS 1.3 | ECDHE-256 | Operator/external HTTPS traffic; the gRPC agent-plane transport is plaintext by default (see the callout below) | Certificate: every 90 days (auto-renewed) |
Keys listed above are generated using a CSPRNG. MD5, SHA-1 and DES are not used by any primitive in this table — a statement about first-party code in the agent-assembly workspace, not about the full transitive dependency tree, which this hub does not audit.
Authentication flow
⚠️ Gateway auth is off by default. A bare
aa-gatewayboots withAuthMode::Offon its REST/admin surface — the zero-configaasm statuspath (and any other REST/admin route) is served with no credential until an operator explicitly opts in withAA_GATEWAY_AUTH=onand a validAA_JWT_SECRET. This is unrelated to the gRPC agent plane below.aa-api(the dashboard API) defaults auth on; the gateway is the off-by-default surface. See Open core boundary for how this pairs with the self-host posture.
How the agent plane is actually authenticated. The credential-token interceptor is fail-closed on five services — audit, approval, topology, secrets and invalidation. It is not applied to
PolicyServiceorAgentLifecycleService; those two are mounted with an enrichment interceptor that never rejects. That does not make them open: their handlers validate directly.Registerrequires theagent_id’sdid:keyto encode the supplied public key (rejected asUnauthenticatedotherwise), consumes a single-use identity-bound server nonce, and verifies an Ed25519 possession proof — so an agent can only register as an identity whose private key it holds.Heartbeat,Deregisterand the control stream validate the token, andCheckActionrejects a missing or mismatched credential token for an agent that is registered, including cross-identity reuse.The one real gap: an unregistered caller presenting an empty token can still obtain a policy evaluation. Tenancy is not taken from the request in that case — it is neutralised to anonymous — so the caller gets a decision without an authenticated identity or tenant. Treat reachability of the port as the control that matters, and see the STRIDE table above.
SDK to gateway (gRPC)
sequenceDiagram autonumber participant SDK as Language SDK participant GW as aa-gateway SDK->>GW: RequestChallenge(agent_id, public_key) GW-->>SDK: nonce (single-use, server-random) SDK->>GW: Register(agent_id, public_key, possession_proof = sign(nonce)) GW->>GW: Verify Ed25519 signature over nonce (one-time possession proof) GW-->>SDK: credential_token (random UUID, no expiry) Note over SDK,GW: All subsequent calls carry credential_token in gRPC metadata (x-aa-credential-token or Authorization: Bearer) SDK->>GW: CheckPolicy(event) [+ credential_token] GW->>GW: Constant-time compare against stored token (no TTL — tokens do not expire) GW-->>SDK: PolicyDecision
Operator authentication
Operators authenticate to the REST/admin surface with an API key or a JWT — the same two mechanisms listed in Cryptographic primitives above. There is no third path.
🗺️ Operator SSO is planned and not implemented. This page previously carried a SAML/OIDC sign-in sequence against a hosted console. Neither exists: there is no SSO implementation anywhere in
aa-api,aa-gateway, oraa-auth, and no console to sign in to. Treat identity federation as a design intent — see Open core boundary — not as an available control, and do not plan an IdP integration against it.
Secrets management
🗺️ The managed secret vault this section previously described does not exist. It claimed AES-256-GCM encryption at rest under a master key in a SaaS control-plane HSM, with rotation from a console. There is no AES-256-GCM implementation in the workspace crates, no HSM or KMS integration, and no console.
Do not treat this stack as a secret store. That advice is unchanged, but the precise state is narrower than “nothing ships” — and less reassuring:
- A store type exists and is mounted. There is a
SecretsStoretrait with one implementation,InMemorySecretsStore, wired into both the API and the gateway. - It is always empty in a shipped build. Both production constructions
instantiate it empty, and nothing can populate it: there is no registration
route in the OpenAPI surface and no
aasm secretscommand. Every${NAME}placeholder therefore resolves toUnknownPlaceholderand the request fails (HTTP 422 / gRPCFailedPrecondition). - Where resolution does succeed, the caller receives the plaintext. The
resolver substitutes
${NAME}tokens and returns the post-substitution arguments to the caller, rather than the gateway making the outbound call itself. The agent process ends up holding the raw credential. The audit entry records placeholder names only, so the credential is not written to the audit stream — but the agent has it. - Nothing encrypts it. In-memory only, no encryption at rest, no key management.
So if you self-host, secrets reaching the gateway, proxy, or SDK are handled by whatever you supply them through — environment, file, or your own secret manager — and their protection is your deployment’s responsibility, not this software’s. Whether this surface is completed or removed is an open decision; this page describes only its current state.
Audit log
Policy decisions and agent-reported events are appended to a JSON Lines audit
file, one line per entry. Database tables (audit_events, audit_logs) hold a
mirror of those records for querying.
⚠️ One fixed file, not per-session files. The audit path names files
{agent_id}-{session_id}.jsonl, but both shipped gateway serve paths pass the constants"gateway"and"default", so a real deployment produces a singlegateway-default.jsonlthat grows without bound. The per-session capability exists in the code and is never exercised in production. If you are planning retention, rotation, or per-tenant separation around per-session files, you will not get them. The properties below are stated precisely, because “immutable audit log” is a claim a security reviewer should be able to check rather than take on trust. All of it is verifiable against the Apache-2.0agent-assemblysource.
The JSONL file is hash-chained, and you can verify it yourself. Each entry
carries a SHA-256 digest over its own fields plus the preceding entry’s digest
(aa-core/src/audit.rs). An operator can check a file end to end with:
aasm audit verify-chain <path-to-session>.jsonl
which reports the number of entries verified, or fails naming the first bad index.
The chain is unkeyed, so bound what it proves. There is no log-signing key, no HMAC, no signature, and no external anchoring over audit records anywhere in the codebase. The chain detects casual or partial modification; it does not resist an actor who can rewrite the file, because that actor can recompute a fully valid chain. It also proves only that the entries present are internally consistent — not that every action produced one.
The database mirror carries no chain metadata. The runtime-to-storage
conversion deliberately drops seq, previous_hash, and entry_hash, and neither
audit_events nor audit_logs has a column for them. There is no verification
routine that can run against either table — chain verification applies to the
JSONL files only.
The log is append-only by convention, not by constraint. Retention pruning
issues DELETE FROM audit_events against rows older than the cutoff in both the
SQLite and Postgres drivers; a backfill migration has issued UPDATE against
audit_logs; and the offline spill buffer evicts its oldest events when it hits
its cap. There is no database trigger, revoked grant, or WORM setting preventing
deletion or update, and the JSONL file is appended without fsync.
Emission is best-effort and decoupled from enforcement. Entries are handed to a bounded in-process channel with a non-blocking send; on backpressure the entry is dropped and the action proceeds anyway. A crash before flush loses whatever is still buffered. Budget debits emit no dedicated audit entry at all — the budget event types exist in the schema but are never constructed, so a debit is visible only via the surrounding decision entry, which is itself droppable. Absence of an entry is therefore not proof that an action did not occur.
A dropped entry is now distinguishable from tampering. The chain head only
advances when an entry is actually written, so a drop leaves a visible sequence-
number gap: verify-chain reports it as INCOMPLETE — hashes and links intact,
some entries never arrived — separately from FAIL, which means an entry’s hash
or its link to the previous entry doesn’t match (alteration or removal). Treat
FAIL as “investigate a compromise” and INCOMPLETE as “investigate a capacity
event”, not the other way around. The distinction has two residual gaps worth
knowing: a tail loss (nothing written after it) and a prefix loss (nothing
written before it, indistinguishable from a chain that legitimately resumed
mid-sequence after a restart) both still read the same whether the cause was a
drop or a deletion — an interior gap is where the distinction holds.
Retention is an operator-set policy, not a per-tenant setting. The storage drivers apply a retention policy that prunes rows past a configured age. This hub does not publish a default retention period or a per-plan retention figure — there is no managed service to enforce one.
Export is via the CLI, in three formats. aasm audit export --format <csv|json|jsonl> writes CSV, a JSON array, or JSON Lines; --format is required
and has no default. A second command, aasm audit compliance-export, emits
compliance-shaped records and can prepend a framework metadata header. JSONL is the
format to prefer for a SIEM or a cold-storage archive, because each line parses
independently and the stream appends without re-rendering.
There is no CEF output and no direct SIEM integration — feeding a SIEM means ingesting an exported file. See the core CLI docs for the full command surface.
Compliance posture
AI Agent Assembly holds no compliance certification, and no compliance assessment has been completed. No SOC 2, ISO 27001, or equivalent audit has been performed against the product or against a managed service. No Data Processing Agreement or Business Associate Agreement is available.
This section previously published a certification status table with a target date. There was no audit report, assessment scope, or executed legal template behind any row of it, so the table was removed rather than relabelled — a status table in a compliance section reads to a procurement reviewer as a programme with a trajectory, which is itself the claim.
What this page can tell a security reviewer is what the system does: the layered defense model, the STRIDE analysis, the cryptographic primitives actually in use, and the audit log’s real integrity properties — all documented above, and all verifiable against the Apache-2.0 source.
The SaaS claim publication checklist records what has to exist before any certification or legal-instrument claim is published here, and who must approve it.
Related documentation
- Why AI Agent Assembly? — competitive positioning and governance differentiation
- Managed control plane (design preview) — the managed control plane, which is planned and not available
- Open core boundary — what is in the Apache-2.0 core today vs. intended for the commercial tier
- Source of truth & status — the canonical maturity label for every area of this hub
- SaaS claim publication checklist — what must be evidenced before managed-service or compliance claims are published
Talk to the team about the STRIDE model and the audit log's integrity properties. Registering interest is not a purchase or a commitment by either side, and no compliance certification, DPA or BAA is available today.
Last reviewed: 2026-08-06 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Why AI Agent Assembly?
AI Agent Assembly is a governance layer for AI agents — a control that sits in the agent’s action path and enforces policy, tracks cost, and intercepts unsafe actions (unsafe tool calls, network egress, and budget overruns) before they execute. Think of it as a security checkpoint in front of each governed agent action — the tool calls your SDK wraps and the outbound requests routed through its proxy — not a dashboard that reports on actions after they happen. Which actions reach that checkpoint depends on how the agent is wired up and launched; see Known limitations. That category distinction is what this comparison is about.
This page helps readers see where AI Agent Assembly fits next to other tools in the AI governance and observability space. All competitor data is taken from each vendor’s public documentation as of 2026-05-05.
In short: most tools in this space observe what an agent did after the fact. AI Agent Assembly is built to enforce policy before an action runs. The sections below show where that difference matters, and where competitors are ahead.
Feature matrix
Because AI Agent Assembly is an enforcement control rather than a pure observability or monitoring tool, the rows below span both categories: the observability rows show that it still gives you the visibility those tools provide, while the policy-enforcement, access-control, and budget-enforcement rows show the security-checkpoint capabilities that monitoring-only tools do not have. Read the matrix with that framing — equal coverage on observability, decisive coverage on enforcement.
Each row is a capability. The columns are AI Agent Assembly (AAASM), Langfuse, Helicone, Opik, and Pillar Security.
Legend: ✓ = full support · partial = limited or gated behind a paid tier · ✗ = not available · n/a = not applicable to the product category.
🚧 Coming soon. Rows marked ✓ 🚧 in the AAASM column describe the AAA-Commercial (Enterprise) tier and the paid SaaS platform that delivers it — both are planned and not yet generally available. See Open core boundary for what ships today versus what is intended design.
| Capability | AAASM | Langfuse | Helicone | Opik | Pillar Security |
|---|---|---|---|---|---|
| Observability | |||||
| LLM call tracing (latency, tokens, cost) | ✓ | ✓ | ✓ | ✓ | partial |
| Multi-turn conversation tracing | ✓ | ✓ | partial | ✓ | ✗ |
| Agent lineage / parent-child spans | ✓ | ✓ | ✗ | partial | ✗ |
| SIEM export (JSON / CEF) | ✓ 🚧 | ✗ | ✗ | ✗ | partial |
| Policy enforcement | |||||
| Pre-execution allow / deny (runtime block) | ✓ | ✗ | ✗ | ✗ | partial |
| Policy-as-code (YAML / JSON versioned rules) | ✓ | ✗ | ✗ | ✗ | ✗ |
| Network-level interception (no agent code change) 1 | ✓ (aa-proxy) | ✗ | ✗ | ✗ | ✗ |
| Kernel-level bypass detection (eBPF) 2 | ✓ | ✗ | ✗ | ✗ | ✗ |
| PII / secret detection at gateway | ✓ (regex rules) | partial (post-hoc) | ✗ | partial (evaluators) | ✓ |
| Vault-backed secrets management | |||||
| Secrets vault integration | ✗ | ✗ | ✗ | ✗ | ✓ |
| Secret scanning in prompts / outputs | partial (regex policy) | ✗ | ✗ | ✗ | ✓ |
| Multi-language SDK | |||||
| Python SDK | ✓ | ✓ | ✓ | ✓ | ✓ |
| TypeScript SDK | ✓ | ✓ | ✓ | ✓ | partial |
| Go SDK | ✓ | ✗ | ✗ | ✗ | ✗ |
| BYO-LLM (provider agnostic) | |||||
| Works with any LLM provider | ✓ | ✓ | ✓ | ✓ | ✓ |
| Open-source SDK core (Apache-2.0) | ✓ | ✓ (MIT) | ✗ | ✓ (Apache-2.0) | ✗ |
| Access control (RBAC) | |||||
| Role-based access control | ✓ 🚧 (Owner/Admin/Developer/Viewer) | partial | partial | partial | ✓ |
| SAML 2.0 / OIDC SSO | ✓ 🚧 | partial (Enterprise) | partial (Enterprise) | partial (Enterprise) | ✓ |
| SCIM user provisioning | ✓ 🚧 | ✗ | ✗ | ✗ | partial |
| Approval workflows | |||||
| Human-in-the-loop approval gates | partial (policy deny; alerting 🚧) | ✗ | ✗ | ✗ | ✓ |
| Automated approval routing | ✗ | ✗ | ✗ | ✗ | ✓ |
| Cost analytics | |||||
| Per-team token / cost budgets (enforced) | ✓ | partial (tracking only) | ✓ (tracking + alerts) | partial (tracking only) | ✗ |
| Budget enforcement (hard deny on exceed) | ✓ | ✗ | ✗ | ✗ | ✗ |
| Audit log integrity | |||||
| Hash-chained, verifiable audit log 3 | partial (unkeyed SHA-256 chain over the JSONL sink) | ✗ | ✗ | ✗ | partial |
| Audit log retention > 30 days | ✓ 🚧 (up to 1 year, Enterprise) | partial (30 days free) | partial | partial | ✓ |
| On-premises / self-hosted option | |||||
| Self-hosted deployment | partial (limited-function OSS) | ✓ | ✗ (SaaS only) | ✓ | ✓ |
Where we currently lag
These are capabilities competitors offer that AI Agent Assembly does not yet fully deliver.
- Vault-backed secrets management — Pillar Security provides first-class secrets vault integration with automatic secret rotation and injection. AAASM currently supports secret-pattern detection via regex policies but does not integrate with HashiCorp Vault or AWS Secrets Manager.
- Automated human-in-the-loop approval workflows — Pillar Security provides structured approval routing with escalation chains. AAASM can deny today (alert emission is planned, not yet shipped — see 🚧) and does not yet route decisions to a named approver queue.
- Full-function self-hosted deployment — Langfuse, Opik, and Pillar Security offer a fully self-hostable product. AAASM self-hosting is limited-function today: a limited stack runs locally from the Apache-2.0 crates (Docker Compose) for evaluation and development, while the complete feature set is delivered via SaaS (see Open Core Boundary).
- Evaluation frameworks and LLM-as-judge scoring — Langfuse and Opik provide built-in evaluation pipelines, dataset management, and automated LLM-as-judge scoring for output quality. AAASM’s policy engine operates on patterns and metadata, not semantic quality.
- Prompt management and versioning — Langfuse provides a managed prompt registry with version history and A/B comparison. AAASM does not include a prompt registry.
Where we lead
These are capabilities where AI Agent Assembly is uniquely strong or differentiated.
- Pre-execution runtime enforcement — AAASM is the only product in this comparison that makes binding allow/deny decisions before an agent action executes. All others are observability tools that record what happened after the fact.
- Kernel-level bypass detection via eBPF —
aa-ebpfreads TLS plaintext at the OpenSSL library level using Linux uprobes, surfacing bypass attempts that SDK-only solutions cannot see. It is a detection layer: the probes emit telemetry and return no verdict, so they report an action rather than preventing it, and they need a Linux host with an OpenSSL-linked process (the file-I/O kprobes additionally are x86_64-only). No competitor in this matrix offers kernel-level visibility at all. - Network-layer interception without agent code changes —
aa-proxyperforms MitM HTTPS interception using per-host certificates minted from a local root CA. Governance can be applied to agents that do not use the SDK, provided the agent process is launched so that it routes through the proxy and trusts that CA. No competitor supports sidecar-proxy-level enforcement. - Policy-as-code with GitOps workflow — AAASM policies are YAML/JSON documents that can be versioned, reviewed, and deployed via standard Git workflows. No competitor in this matrix offers a structured policy language; guardrails in other tools are typically configured through UI forms or proprietary DSLs.
- Hash-chained, verifiable audit log — each entry in the per-session JSONL log carries a SHA-256 digest over its own fields plus the preceding entry’s digest, and
aasm audit verify-chainre-walks that chain. This ships in the open-source build, not behind an Enterprise flag. Read the guarantee precisely, because compliance work (PCI-DSS, SOC 2 Type II) depends on the difference: the chain is unkeyed, so it detects accidental or careless alteration but is not a signature — anyone who can rewrite the log can recompute the chain. It covers the JSONL sink only; the database mirror stores no chain metadata. The log is append-only by convention, not by constraint — retention pruning deletes rows — and emission is best-effort, so a dropped entry is indistinguishable from tampering. See Audit log for the exact bounds. No competitor in this matrix offers a verifiable chain.
Competitor documentation references
Last validated 2026-05-05 against each vendor’s documentation as of that date.
| Competitor | Documentation URL |
|---|---|
| Langfuse | https://langfuse.com/docs |
| Helicone | https://docs.helicone.ai |
| Opik | https://www.comet.com/docs/opik |
| Pillar Security | https://docs.pillar.security |
Related documentation
- Security model — STRIDE threat model, IronClaw defense
- Open core boundary — what is OSS vs. enterprise
- Managed SaaS onboarding — design preview — planned, not available
Last reviewed: 2026-08-06 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
-
No change to your agent’s code, but the agent process must be launched so that it honours
HTTP_PROXY/HTTPS_PROXYand trusts the proxy’s local root CA. On macOS the install is attempted at proxy start and shells out tosecurity add-trusted-cert, which requires admin authorization — macOS prompts, and a refusal fails proxy startup. On Linux runsudo aasm proxy install-ca. Windows is unsupported. Interception is HTTP/1.1 only — HTTP/2, gRPC, and WebSocket are out of scope — and by default only the built-in LLM provider hosts are decrypted; other hosts are tunnelled uninspected unless you list them. ↩ -
Detection, not prevention: the probes emit telemetry and return no verdict, so an action they see is one that already happened. TLS visibility covers OpenSSL-linked processes only, and the layer requires Linux with a kernel that supports it, BTF, and a reachable loader daemon — the file-I/O kprobes additionally are x86_64-only. It degrades with a warning rather than failing closed if it cannot attach. ↩
-
Tamper-evident, not tamper-proof, and not immutable. Each entry in the per-session JSONL log carries a SHA-256 digest over its own fields plus the preceding entry’s digest;
aasm audit verify-chainre-walks it. The chain is unkeyed — there is no HMAC, no signature and no external anchoring — so it detects careless or accidental alteration but not an attacker who can rewrite the file and recompute the chain. It covers the JSONL sink only; the database mirror stores no chain metadata. The log is append-only by convention rather than by constraint (retention pruning deletes rows), and emission is best-effort, so a dropped entry is indistinguishable from tampering. Full bounds in Audit log. ↩
Open core boundary
AI Agent Assembly follows an open-core model. The line is simple:
- Enforcement is open source. The interception mechanisms, policy engine, SDK shims, and CLI are Apache-2.0. Anyone can read, audit, and contribute to them.
- Enterprise operations are intended to be commercial. Capabilities such as SSO, SCIM, advanced audit, and multi-region data residency are planned for a commercial tier. That tier is not available, its licence terms are not published, and there is no paid plan to buy.
🗺️ The commercial side of this boundary is planned, not available. You can self-host a limited-function stack from the Apache-2.0 crates today — using the published Docker Compose example — for local evaluation and development. The managed service that is intended to deliver the commercial capabilities is not running; see Managed control plane (design preview) and Managed SaaS onboarding (design preview) for what that means in practice, and Source of truth & status for the canonical maturity label.
Why open core?
The enforcement path must be inspectable
The infrastructure that sits between AI agents and the outside world has to be trustworthy and independently auditable. Keeping the core open source is not a marketing choice — it follows directly from the security posture.
An enterprise cannot take our word for how the policy engine evaluates rules, how eBPF probes intercept system calls, or how the sidecar proxy terminates TLS. Open source means a third party can read, review, and verify the enforcement path without involving us.
A single boundary rule
The split between open and commercial follows one principle: enforcement is open; enterprise operations are commercial.
- If a feature controls what agents can do, it belongs in the Apache-2.0 core.
- If a feature controls how operators manage, scale, or audit the system at enterprise grade — identity federation, directory-driven user lifecycle, longer-retention and higher-assurance audit storage, regional deployment control — it belongs in the commercial tier.
A motivated team can fork, read, or contribute to the security controls listed as Apache-2.0 below, regardless of subscription status.
Open source strengthens the core
Open-sourcing the enforcement logic creates a community feedback loop. Security researchers who find a gap in the policy engine, proxy TLS handling, or an eBPF program can open an issue or send a pull request.
We chose Apache-2.0 specifically because it permits commercial integration without a copyleft obligation — SDK users can embed the shims in proprietary products without the license spreading to their own code.
Limited-function self-host today; managed service planned
Shipping the crates as open source lets teams read, audit, and contribute — and self-host a limited-function stack (via the published Docker Compose example) for local evaluation and development.
The enterprise-operations capabilities are intended to be delivered as a managed service rather than as self-managed software, because operating a multi-tenant platform takes infrastructure and on-call capability that a self-managed install does not get for free. That is a design intent, not a shipped service: the managed platform is not running, and this hub publishes no availability, support, or compliance commitment for it.
What is in the Apache-2.0 core today
These ship in the public agent-assembly monorepo and the three SDK repos, under
Apache-2.0 (the python-sdk shim is MIT — see crate licensing).
They run without any managed service.
| Area | In the Apache-2.0 core |
|---|---|
| Interception | Language SDKs (Python, TypeScript, Go); sidecar proxy (aa-proxy); eBPF sensor (aa-ebpf, Linux) |
| Gateway and policy | Agent registry; policy engine (allow/deny/audit); policy-as-code (YAML/JSON); budget limits declared in policy and enforced by the gateway — see Policy reference |
| Authentication | API key authentication |
| Audit | Audit event emission and query — see Security model |
| Operations | aasm operator CLI; limited-function local stack via the published Docker Compose example; health probes and Prometheus metrics — see Self-host observability |
The public issue trackers and pull-request queues on github.com/ai-agent-assembly are open to anyone. They are not a support channel with a response commitment.
What is intended for the commercial tier
🗺️ Planned — not available. Everything in this section is design intent. The commercial tier is not for sale, its licence terms are not published, and the managed service that would deliver it is not running. This is not a roadmap commitment, a delivery date, or an offer.
Identity federation, directory-driven user provisioning, longer-lived and higher-assurance audit storage, audit export into external security tooling, and regional deployment control are the capability areas intended to sit on the commercial side of the boundary — because they are operator-management concerns rather than enforcement controls.
This hub deliberately does not publish, for any of them: a plan or tier they belong to, a price, a quota, a retention period, a region list, a data-residency guarantee, an availability or support commitment, or a compliance certification. The SaaS claim publication checklist records what has to be evidenced, and by whom, before any of that can be published.
Crate licensing
All Cargo crates in the agent-assembly workspace are Apache-2.0:
| Crate | License | Notes |
|---|---|---|
aa-core | Apache-2.0 | Core domain types — always OSS |
aa-proto | Apache-2.0 | Protobuf definitions — always OSS |
aa-runtime | Apache-2.0 | Async runtime utilities — always OSS |
aa-gateway | Apache-2.0 | Gateway with policy engine — always OSS |
aa-api | Apache-2.0 | REST API surface — OSS |
aa-proxy | Apache-2.0 | Sidecar proxy — always OSS |
aa-ebpf | Apache-2.0 | eBPF user-space loader — always OSS |
aa-ebpf-common | Apache-2.0 | eBPF shared types — always OSS |
aa-wasm | Apache-2.0 | WebAssembly build — always OSS |
aa-cli | Apache-2.0 | aasm operator CLI — always OSS |
conformance | Apache-2.0 | Conformance test suite — always OSS |
The three SDK native-binding shims are not members of the agent-assembly Cargo
workspace — each lives in its own SDK repo and carries that repo’s own license:
| Crate | Repo | License | Notes |
|---|---|---|---|
aa-ffi-python | python-sdk (native/aa-ffi-python) | MIT | Python SDK native shim — the python-sdk repo is intentionally MIT, not Apache-2.0 |
aa-ffi-node | node-sdk (native/aa-ffi-node) | Apache-2.0 | TypeScript SDK native binding |
aa-ffi-go | go-sdk (native/aa-ffi-go) | Apache-2.0 | Go SDK native shim |
Apache 2.0 key terms
The Apache License 2.0 grants users the right to use, reproduce, prepare derivative works, distribute, and sublicense the software with or without modification. It does not grant trademark rights, and it requires preservation of copyright notices and attribution in distributed works. See the full license text at https://www.apache.org/licenses/LICENSE-2.0.
The commercial capabilities described above are intended to be delivered by the managed control plane rather than by separate closed-source crates, so the boundary is a deployment boundary rather than a second licence over the enforcement path. The interception and enforcement logic listed in What is in the Apache-2.0 core today is Apache-2.0.
Contributing to the OSS core
The Apache-2.0 crates welcome community contributions. See CONTRIBUTING.md in the agent-assembly repository for:
- Branching and commit conventions
- How to run the test suite (
cargo nextest run --workspace) - The CLA requirement for non-trivial contributions
- How to file issues and feature requests
Requests for the capabilities intended for the commercial tier are tracked internally by the AI Agent Assembly team. Filing one is not a delivery commitment.
Related documentation
- Security model — cryptographic primitives and audit log details
- Managed control plane (design preview) — the managed control plane, which is planned and not available
- Source of truth & status — the canonical maturity label for every area of this hub
- SaaS claim publication checklist — what must be evidenced before commercial-tier claims are published
- Why AI Agent Assembly? — open-source posture vs. competitors
Last reviewed: 2026-08-06 · AI Agent Assembly Team
This page describes a licensing and deployment boundary. It is not legal advice and it is not a licence grant beyond the Apache-2.0 terms of the published crates. Commercial-tier licence terms are not published; nothing here creates one.
Last updated: 2026-09-07 by AI Agent Assembly Team
FAQ
Answers to the questions first-time visitors ask before reading any of the detailed pages. Each answer links to where the topic is covered in full.
What is AI Agent Assembly, in one sentence?
It is a governance layer for AI agents: it sits between your agents and the outside world and enforces policy, tracks cost, and intercepts unsafe actions before they run. See the Introduction and Why AI Agent Assembly?.
Do I have to change my agent’s code?
Not necessarily. Three interception mechanisms exist, they are deployed independently rather than as an ordered pipeline, and you can pick how invasive to be:
- The SDK layer needs a small amount of instrumentation in your code and is the fastest path.
- The sidecar proxy (
aa-proxy) governs an agent’s network traffic without modifying the agent’s own source, on traffic that is routed to it and trusts its CA. - The eBPF sensor (
aa-ebpf, Linux only) observes activity at the kernel level and reports it. It is observe-only: it returns no verdict and blocks nothing.
See the interception mechanisms in the Introduction.
Does it work with my LLM / framework?
Yes — it is provider-agnostic. It governs agents regardless of which model provider you use, and ships SDKs for Python, TypeScript/Node, and Go, with framework examples (LangChain, LlamaIndex, bare OpenAI, and more) in the examples repository.
Is it free? What is open source vs. paid?
The enforcement core — the interception mechanisms, policy engine, SDKs, and CLI — is open source under Apache-2.0. Enterprise operations (SSO, SCIM, tamper-evident audit, dedicated regions, SLAs) are commercial and delivered on paid SaaS tiers. See the Open core boundary.
Can I self-host it?
You can self-host a limited-function stack from the open-source crates (using the published Docker Compose example) for local evaluation and development. The complete feature set is delivered through the AI Agent Assembly cloud (SaaS). See the Open core boundary.
How does it actually block an unsafe action?
Before an agent action runs, the gateway evaluates your policy and returns an allow or deny decision; a deny stops the action. Budgets are enforced the same way — once a team is over budget, further calls are denied. See the Policy reference.
How is it different from an observability / tracing tool?
Observability tools record what an agent did, after the fact. AI Agent Assembly makes a binding allow/deny decision before the action executes. The comparison page maps this against other tools.
Where do I get started right now?
Clone the examples repository and run a governed agent end to end — that is the path you can run today. The managed service is planned and not available.
What do the acronyms mean (eBPF, SCIM, mTLS, STRIDE…)?
See the Glossary, which defines every recurring term and acronym in plain language.
Last updated: 2026-09-07 by AI Agent Assembly Team
Managed SaaS onboarding — design preview
🗺️ Design preview — planned, not available. The AI Agent Assembly managed SaaS platform has no public signup, no published plans or prices, and no service commitments. Nothing on this page is purchasable or usable today, and nothing here is an offer, a quote, or a contractual commitment.
This page is for readers evaluating whether to wait for a managed workspace or to start on the open-source stack now. It deliberately does not contain onboarding steps.
An earlier version of this page walked through managed-workspace onboarding: tier selection, quotas, region selection, console screens, credential issuance, support channels, procurement, and legal-agreement handling. Those instructions described a service that is not running, so they were removed rather than restated in vaguer language. The SaaS claim publication checklist records each removed claim, the owner who must approve restoring it, and the evidence that approval requires.
For the canonical maturity and visibility label of every area of this hub — including Cloud — see Source of truth & status.
What you can run today
The open-source stack is what ships. It is Apache-2.0, public, and versioned as
v0.0.1-rc (see the compatibility matrix for the exact
component versions that work together).
| To do this | Go here |
|---|---|
| Run the gateway, policy engine, proxy, or CLI | core docs |
| Instrument a Python agent | Python SDK docs |
| Instrument a TypeScript agent | Node SDK docs |
| Instrument a Go agent | Go SDK docs |
| Run a limited-function stack locally with Docker Compose | Docker & containers |
| Read the policy rule schema the gateway evaluates against | Policy reference |
| Step through a working governed agent end to end | examples repo |
The self-hostable stack is limited-function and intended for local evaluation and development. Open core boundary describes which capabilities are in the open-source core and which are intended for the commercial tier.
The onboarding journey this is designed for
Everything in this section is design intent. It names no plan, price, quota, region, retention period, console screen, tenant-identifier format, availability commitment, or date, because none of those exist — see what this page does not publish.
The managed service is intended to deliver the operator-management capabilities that sit on the commercial side of the open core boundary: identity federation, directory-driven user provisioning, longer-lived and higher-assurance audit storage, audit export into external security tooling, and regional deployment control. The enforcement path itself is Apache-2.0 and needs none of them.
The dependency that shapes the whole journey: enforcement does not wait on the control plane. A team adopting the managed service later runs the same gateway, policy engine, proxy and SDK shims it runs today; the managed service is intended to add operator management around them, not to replace them. That is why the available path above is not a stopgap.
What this page does not publish, and why
The managed service is not running, so this hub does not publish:
- Plan or tier names, prices, or what any plan includes.
- Agent, policy, or retention quotas.
- Regions, region selection, or data-residency guarantees.
- Availability, uptime, or support-response commitments.
- Billing, invoicing, purchase-order, or procurement-timeline instructions.
- Onboarding steps that reference a console, signup form, or credential screen.
- Compliance certifications, or the availability of a DPA or BAA.
Each of these is tracked in the publication checklist with the evidence needed to publish it. Publishing any of them before that evidence exists would misrepresent the product.
What must be true before any of this is published
This hub does not decide when a planned area becomes available; the SaaS claim publication checklist does, one claim class at a time. Each register row names the evidence required and the approval owner who must sign the specific wording.
Two things gate the whole page rather than one row: the managed service running and carrying real traffic, and the status map moving this area off 🗺️ Planned. Until both hold, no register row can be satisfied, because every one of them requires evidence produced by a running service.
Evidence
- Maturity: Source of truth & status’s Operations (running & onboarding) row — 🗺️ Planned.
- Claim record: this page’s
AA-PAGE-METAcarries a single ADR 0033 §6Plannedclaim, withplatforms: []and noavailabilityvalue — the metadata form for a capability present in no published artifact. - Removed claims and their restoration conditions: the publication checklist register.
- There is no implementation to link: the
cloudrepository is private and outside this hub’s public content boundary. A design deep-dive here would describe a system no reader can verify.
Related documentation
- Source of truth & status — which areas ship today and which are planned
- Open core boundary — the open-source / commercial split
- Managed control plane — design preview — planned, not available
- SaaS claim publication checklist — what must be evidenced before managed-service claims return
Open the examples repo and step through a governed
LangChain, LlamaIndex, or bare-OpenAI agent end-to-end.
Registering interest is not a purchase, a reservation, or a commitment by either side. The open-source stack above works today.
Last reviewed: 2026-09-07 · AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Managed control plane — design preview
🗺️ Design preview — planned, not available. The AI Agent Assembly managed control plane (Cloud) is not running. There is no workspace to provision, no console to log into, and no service, support, or compliance commitment attached to it. Nothing on this page is an offer or a contractual commitment.
This page is for enterprise platform, security, and procurement readers who need to know what the managed control plane does not yet provide, so they can plan against the open-source stack instead of against an unavailable service.
An earlier version of this page documented the managed platform as if it were operating: a region list with data-residency guarantees, tenant provisioning paths, per-tier quotas, SSO and SCIM configuration walkthroughs, a console budget form, an availability-and-support SLA table, card and invoice billing setup, and the handling of Data Processing Agreements and Business Associate Agreements. None of those had a running service, an approved commercial policy, or a legal review behind them, so they were removed rather than reworded into softer promises.
Every removed claim is listed in the SaaS claim publication checklist, together with the owner who must approve restoring it and the evidence that approval requires. Source of truth & status carries the canonical maturity label for Cloud and for every other area of this hub.
What runs today instead
Governance enforcement is open source and does not depend on the managed control
plane. The gateway, the policy engine, the sidecar proxy, the eBPF sensor, the
SDK shims, and the aasm CLI are Apache-2.0 and can be run locally.
| Concern | Where it is documented today |
|---|---|
| Running the gateway, proxy, sensor, and CLI | core docs |
| Bringing up a limited-function stack with Docker Compose | Docker & containers |
| Health probes and Prometheus metrics for that stack | Self-host observability |
| The policy rule schema the gateway evaluates against | Policy reference |
| Spend caps | Policy reference → budget — per-agent and per-organisation USD limits declared in policy |
| Authentication that exists today | API keys, as described in Open core boundary |
| Which capabilities are open source and which are intended for the commercial tier | Open core boundary |
The console budget form this page previously described did not match the budget schema the gateway actually validates against. Policy reference is the source of truth for budget behaviour.
The control-plane design this is intended for
Everything in this section is design intent. It names no region, tenant format, quota, plan, price, SLA, or date, because none of those exist — see what this page does not publish.
The managed control plane is intended to add the operator-management capabilities that sit on the commercial side of the open core boundary: identity federation, directory-driven user provisioning, longer-lived and higher-assurance audit storage, audit export into external security tooling, and regional deployment control. The reason managed delivery is the intent rather than a self-managed distribution of the same code: multi-tenant infrastructure and on-call operation are what a self-managed install does not get for free.
The dependency that shapes the design: enforcement does not wait on the control plane. The gateway, policy engine, proxy, and SDK shims a team runs today are the same ones a managed workspace would run underneath — the control plane is intended to add operator management around them, not to replace them.
What this page does not publish, and why
Because the managed control plane is not running, this hub does not publish:
- Regions, region selection, or data-residency guarantees.
- Tenant or workspace provisioning steps, or a tenant-identifier format.
- Plan or tier names, prices, or per-tier quotas for agents, policies, or audit-log retention.
- SSO (SAML 2.0 / OIDC) or SCIM 2.0 configuration instructions, endpoints, or supported-operation matrices.
- A console role model, or group-to-role mapping instructions.
- Availability, uptime, or support-response commitments, or service credits.
- Billing, invoicing, payment-method, purchase-order, or payment-terms instructions.
- Compliance certifications, or the availability of a DPA or a BAA.
Publishing any of these before the corresponding service, owner approval, and evidence exist would present an unavailable service as a defined one. The publication checklist names the evidence required for each.
What must be true before any of this is published
This hub does not decide when a planned area becomes available; the SaaS claim publication checklist does, one claim class at a time. Each register row names the evidence required and the approval owner who must sign the specific wording.
Two things gate the whole page rather than one row: the managed control plane running and carrying real tenants, and the status map moving this area off 🗺️ Planned. Until both hold, no register row can be satisfied, because every one of them requires evidence produced by a running service.
Evidence
- Maturity: Source of truth & status’s Cloud (SaaS control plane) row — 🗺️ Planned.
- Claim record: this page’s
AA-PAGE-METAcarries a single ADR 0033 §6Plannedclaim, withplatforms: []and noavailabilityvalue — the metadata form for a capability present in no published artifact. - Removed claims and their restoration conditions: the publication checklist register.
- There is no implementation to link: the
cloudrepository is private and outside this hub’s public content boundary. A design deep-dive here would describe a system no reader can verify.
Related documentation
- Source of truth & status — which areas ship today and which are planned
- Managed SaaS onboarding — design preview — managed onboarding, also planned
- Open core boundary — the open-source / commercial split
- Security model — the security posture of the open-source enforcement path
- SaaS claim publication checklist — what must be evidenced before managed-service claims return
Last reviewed: 2026-09-07 · AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Choose your SDK
Pick the SDK for the language your agent is already written in; this page hands you to that SDK’s own documentation, which is where every install step and API surface lives.
Choose your language
Each SDK is a separately versioned program with its own documentation site and its own version selector.
| Language | SDK documentation | Content layer | Component | Documentation-area maturity |
|---|---|---|---|---|
| Python | Python SDK docs | L3 | python-sdk | 🧪 Release candidate |
| TypeScript / JavaScript | Node SDK docs | L3 | node-sdk | 🧪 Release candidate |
| Go | Go SDK docs | L3 | go-sdk | 🧪 Release candidate |
Prefer reading working code first? The runnable examples carry end-to-end walk-throughs for all three languages.
Two different words spelled the same way
The Documentation-area maturity column above is the maturity of a documentation area, read from the status map, which defines what each label means. That page owns the definitions and this one does not restate them.
It is not a statement about what happens to one of your agent’s actions. That is a separate vocabulary, defined once in ADR 0033 §6, and this page makes no claim in it. Verify is the page that maps a published sentence onto that vocabulary.
Before you choose
Choosing a language is not the same decision as choosing where a decision about an action is made, and the second one is made before this page rather than on it. It is an evaluation decision: Choose your enforcement path is where it is made, and Evaluate carries the default posture.
What this page does not do
This page is a router, and it is deliberately thin:
- It does not carry install steps or an API surface. Those belong to each SDK’s own documentation, which is the canonical source for them, and duplicating them here is how the copies drift apart.
- It does not describe a mechanism. What a given component does with an action is documented at depth in the core documentation.
- It does not cover running the stack. That is Operate.
- It does not cover checking a published statement. That is Verify.
Going deeper
The contract every SDK speaks to, and the components behind it, are documented in the core documentation.
Last updated: 2026-08-13 by AI Agent Assembly Team
Run and diagnose
This section routes you through running the stack on a host you control, from starting an agent under it to working out why an expected control did not fire; the commands and configuration themselves live in the pages linked below.
The four questions, in order
Most operator sessions are one of these four, and they are ordered because each one assumes the previous is already true.
| # | The question | Where it is answered | Content layer |
|---|---|---|---|
| 1 | How do I start an agent under this? | core documentation | L3 |
| 2 | How do I install it on this platform? | Docker and containers · core documentation | L2 · L3 |
| 3 | What does the running stack expose? | Self-host observability | L2 |
| 4 | Why did a control not fire? | Troubleshooting | L2 |
Question 1 is first for a reason: what reaches a checkpoint at all depends on how the agent was launched, so an agent started outside that path is a common answer to question 4.
Before you start
Which components you need, and which versions of them work together, are two different lookups and both are in Reference:
- Compatibility matrix pairs versions across components.
- Status map says who owns each area and how mature its documentation is.
- Choose your enforcement path says which governed paths exist, what each one needs on a host, and what each one does not cover.
The managed service is not an operating route today
The Cloud area is marked 🗺️ Planned in the
status map, on the documentation-area axis. There is no managed
service to operate from this section, and the two pages that discuss one
(Managed SaaS onboarding (design preview) and Managed control plane (design preview))
sit in Evaluate, where their reader is someone deciding what to tell a stakeholder
rather than someone running it.
Those pages move into this section when, and only when, their area’s Maturity cell in
the status map stops reading 🗺️ Planned. That is a string comparison against a
generated table, not a judgement any page may make on its own.
What this page does not do
- It does not describe a mechanism. What each component does with an action is documented at depth in the core documentation.
- It does not restate a command or a configuration key. Those belong to the pages in the table above.
- It does not cover adding a checkpoint to code you are writing. That is Integrate.
- It does not cover checking a published statement against its evidence. That is Verify.
Going deeper
The components, their configuration and their behaviour are documented in the core documentation.
Last updated: 2026-09-07 by AI Agent Assembly Team
Docker & Containers
This page is the reference for running AI Agent Assembly from published container images — which images exist, how they are tagged, how to pull and run the gateway and runtime, how to build your own agent image on top of the SDK base images, and how to wire the governed agent + sidecar + gateway topology so it works out of the box.
Scope: this covers the limited-function OSS self-host stack, not the managed SaaS. The Apache-2.0 images below let you stand up the enforcement data plane locally for evaluation and development. Full functionality — team budgets, the central registry, the operator dashboard, persistence, SSO/SCIM — remains SaaS-only. See Open core boundary for the split and Managed control plane (design preview) for the managed platform. This is not a production orchestration guide (no Helm / Terraform / Kubernetes).
Published images
Five images are published to the GitHub Container Registry under
ghcr.io/ai-agent-assembly.
All are multi-arch (linux/amd64 + linux/arm64) and carry SLSA build
provenance (see Provenance & verification).
| Image | Role |
|---|---|
ghcr.io/ai-agent-assembly/aa-gateway | The policy/registry brain. Loads a policy file and serves the gRPC API on :50051 — AgentLifecycleService.Register (agent registration) and PolicyService.CheckAction (the per-tool allow/deny decision). This is the policy authority. |
ghcr.io/ai-agent-assembly/aa-runtime | The enforcement sidecar. Owns the SDK IPC socket at /tmp/aa-runtime-<agent_id>.sock, exposes health/metrics on :8080, and forwards each policy check to the gateway. |
ghcr.io/ai-agent-assembly/python | SDK base image for building a Python agent — ships python, the agent-assembly SDK (native extension included), and the aasm CLI. |
ghcr.io/ai-agent-assembly/node | SDK base image for building a Node/TypeScript agent — ships node, the globally installed @agent-assembly/sdk, and aasm. |
ghcr.io/ai-agent-assembly/go | SDK base image for building a Go agent — ships the Go toolchain with the go-sdk pre-installed in the module cache, and aasm. |
Not published as images.
aa-api(the REST/OpenAPI surface) andaa-proxy(the egress-interception proxy) are Apache-2.0 crates in theagent-assemblyrepository but have no published container image — do not expect todocker pullthem. There is no separately runnableaa-apicontainer: the REST surface (/api/v1/health) is only exposed when the gateway is launched in local mode (--mode local), a single-process dev topology not used by the gateway + runtime container stack on this page — that stack runs the gateway in its default legacy gRPC mode, which serves gRPC only on:50051and no HTTP (see Self-Host Observability). To runaa-proxy, build it from source (aa-proxy/Dockerfile).
Image tags
The two service images and the three SDK base images use different tag schemes — the difference is deliberate and easy to get wrong.
Service images (aa-gateway, aa-runtime) are tagged with the release
version plus a moving latest:
ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6 # immutable release tag
ghcr.io/ai-agent-assembly/aa-gateway:latest # moves with each release
SDK base images (python, node, go) are tagged <runtime>-<version> —
the runtime version is part of the tag, so there is no bare :v0.0.1-rc.6
tag on these images. Each also publishes a moving, versionless <runtime> tag
and latest:
| Image | Pinned (release) tags | Moving tags |
|---|---|---|
python | 3.12-slim-v0.0.1-rc.6, 3.13-slim-v0.0.1-rc.6, 3.14-slim-v0.0.1-rc.6 | 3.12-slim, 3.13-slim, 3.14-slim, latest |
node | 20-slim-v0.0.1-rc.6, 22-slim-v0.0.1-rc.6, 24-slim-v0.0.1-rc.6 | 20-slim, 22-slim, 24-slim, latest |
go | 1.24-alpine-v0.0.1-rc.6, 1.25-alpine-v0.0.1-rc.6, 1.26-alpine-v0.0.1-rc.6 | 1.24-alpine, 1.25-alpine, 1.26-alpine, latest |
For reproducible builds, pin the release-tagged variant (or, better, a
@sha256 digest — see Provenance & verification).
Use latest only for throwaway local demos.
Pull and run the gateway and runtime
Pull
docker pull ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6
docker pull ghcr.io/ai-agent-assembly/aa-runtime:v0.0.1-rc.6
Both images are public — no docker login is required to pull.
Run the gateway
The gateway loads a policy file and serves gRPC. Mount your policy and publish
:50051:
docker run --rm \
-v "$PWD/policy.yaml:/etc/aa/policy.yaml:ro" \
-p 50051:50051 \
ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6 \
--policy /etc/aa/policy.yaml --listen 0.0.0.0:50051
| Aspect | Value |
|---|---|
| Args | --policy <path> (section-based policy file), --listen <addr> |
| Port | 50051 — gRPC (Register + CheckAction) |
| Volume | the policy file, mounted read-only |
| User | runs non-root (uid 65532) — see the topology below |
Run the runtime
The runtime owns the SDK IPC socket (on a shared volume) and its health/metrics
server. Point it at the gateway with AA_GATEWAY_ENDPOINT:
docker run --rm \
-e AA_AGENT_ID=my-agent-001 \
-e AA_GATEWAY_ENDPOINT=http://gateway-host:50051 \
-v aa-runtime-socket:/tmp \
-p 8080:8080 \
ghcr.io/ai-agent-assembly/aa-runtime:v0.0.1-rc.6
| Env var | Purpose |
|---|---|
AA_AGENT_ID | Agent identity — determines the socket name /tmp/aa-runtime-<AA_AGENT_ID>.sock. Must match the agent’s AA_AGENT_ID. |
AA_GATEWAY_ENDPOINT | Gateway gRPC endpoint the runtime forwards CheckAction to. Leave unset to enforce locally from a mounted policy file (a coarse, action-type denylist — see the topology note). |
AA_METRICS_ADDR | Bind address for the health/metrics server. Default 0.0.0.0:8080. |
| Aspect | Value |
|---|---|
| Port | 8080 — HTTP /health, /ready, /metrics |
| Volume | a shared /tmp volume so the agent can reach the IPC socket |
| User | runs non-root (uid 65532) |
Health-checking
| Component | Probe | Endpoint |
|---|---|---|
aa-runtime | liveness / readiness / metrics | GET /health, GET /ready, GET /metrics on :8080 |
aa-gateway | TCP liveness | TCP connect to the gRPC port :50051 |
curl -fsS http://localhost:8080/ready # runtime readiness -> "ready"
curl -fsS http://localhost:8080/health # runtime liveness (JSON)
curl -fsS http://localhost:8080/metrics # Prometheus scrape target
Probing the gateway. As launched here (
--policy … --listen 0.0.0.0:50051, no--mode), the gateway runs in legacy gRPC mode: it serves gRPC only on:50051and exposes no HTTP health endpoint — there is no/healthzto curl on this container. On top of that the image is distroless (no shell, nocurl/wgetinside), so a Composehealthcheck: ["CMD-SHELL", …]cannot run inside it either. Probe it from the host with a TCP connect to the gRPC port:50051instead. A standard gRPCgrpc.health.v1.Healthservice on:50051is being added (AAASM-4759); until it ships, a TCP-level check of:50051is the gateway liveness signal.
See Self-Host Observability for the full health, readiness, and Prometheus-metrics surface, including the baseline metric set.
Compose
The minimal runtime-sidecar stack as a Compose snippet:
services:
aa-runtime:
image: ghcr.io/ai-agent-assembly/aa-runtime:v0.0.1-rc.6
environment:
AA_AGENT_ID: "my-agent-001"
AA_POLICY_PATH: "/etc/aa/policy.toml" # local enforcement, no gateway
volumes:
- aa-runtime-socket:/tmp # socket lives at /tmp/aa-runtime-my-agent-001.sock
- ./policy.toml:/etc/aa/policy.toml:ro
ports:
- "8080:8080" # health + metrics
volumes:
aa-runtime-socket:
A complete, runnable version — plus the optional aa-proxy egress profile — is
maintained in the examples repository at
scenarios/live-core-enforcement/.
Build your own agent image
The python, node, and go images are base images: the language runtime
with the Agent Assembly SDK (and the aasm CLI) already installed. Build your
agent by adding only your source on top — no SDK install step needed.
# Python agent on the SDK base image.
FROM ghcr.io/ai-agent-assembly/python:3.13-slim-v0.0.1-rc.6
WORKDIR /agent
COPY agent.py /agent/agent.py
# The base image already provides `python`, the `agent_assembly` SDK
# (native extension included), and the `aasm` binary — so no `pip install`.
CMD ["python", "/agent/agent.py"]
The Node and Go base images work the same way — swap the FROM line for
ghcr.io/ai-agent-assembly/node:22-slim-v0.0.1-rc.6 (a bare require('@agent-assembly/sdk')
resolves via the base image’s NODE_PATH) or
ghcr.io/ai-agent-assembly/go:1.26-alpine-v0.0.1-rc.6 (the go-sdk is already in
the module cache). Reference Dockerfiles for all three languages live in the
agent-assembly repository under
docker/smoke/agents/.
The governed topology
For per-tool allow/deny enforcement, run the full three-piece topology: your
agent, the aa-runtime sidecar, and the aa-gateway. The runtime’s own local
policy is a coarse action-type denylist; only the gateway’s section-based
policy engine can distinguish, say, read_file (allow) from delete_file
(deny).
flowchart LR agent["your agent<br/>(SDK)"] runtime["aa-runtime<br/>sidecar"] gateway["aa-gateway<br/>(policy authority)"] agent -- "register (gRPC :50051)" --> gateway agent -- "check (UDS /tmp/….sock)" --> runtime runtime -- "CheckAction (gRPC :50051)" --> gateway
Two distinct transports are in play: registration is a direct SDK → gateway
gRPC call (the native client reads AA_GATEWAY_ENDPOINT, which defaults to
127.0.0.1:50051 — wrong inside a container, so set it explicitly); the
governed check goes over the runtime’s Unix domain socket, and the runtime
forwards CheckAction to the gateway.
The uid requirement
The runtime creates its IPC socket owner-only (0600) — there is no
group/world access window (a tightened umask guarantees 0600 from the first
instant, closing a TOCTOU gap). Both aa-runtime and aa-gateway run as the
distroless non-root user, uid 65532. Because the socket is 0600, the
agent process must run as the same uid (65532) as the runtime — otherwise it
cannot open the socket and every governed call fails to connect. Aligning the
uid is what makes the stack work out of the box:
services:
aa-gateway:
image: ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6
command: ["--policy", "/etc/aa/policy.yaml", "--listen", "0.0.0.0:50051"]
volumes:
- ./policy.yaml:/etc/aa/policy.yaml:ro
ports:
- "50051:50051"
aa-runtime:
image: ghcr.io/ai-agent-assembly/aa-runtime:v0.0.1-rc.6
depends_on: [aa-gateway]
environment:
AA_AGENT_ID: "my-agent-001"
AA_GATEWAY_ENDPOINT: "http://aa-gateway:50051"
volumes:
- aa-runtime-socket:/tmp
ports:
- "8080:8080"
agent:
build: ./agent
# Align the agent's uid with the runtime's (distroless nonroot = 65532)
# so it can open the 0600 runtime socket. Without this the governed
# calls cannot connect.
user: "65532:65532"
depends_on: [aa-gateway, aa-runtime]
environment:
AA_AGENT_ID: "my-agent-001" # MUST match aa-runtime
AA_RUNTIME_SOCKET: "/tmp/aa-runtime-my-agent-001.sock"
AA_GATEWAY_ENDPOINT: "http://aa-gateway:50051" # native client (registration)
AA_GATEWAY_URL: "http://aa-gateway:7391" # SDK gateway handle (REST control plane)
volumes:
- aa-runtime-socket:/tmp # same socket the runtime created
volumes:
aa-runtime-socket:
Distroless images have no in-container healthcheck. Because
aa-runtimeandaa-gatewayship no shell, gate readiness from the host (poll the runtime’s:8080/readyand the gateway’s:50051) rather than with a ComposeCMD-SHELLhealthcheck — an in-container probe fails withexec: "/bin/sh": no such file or directoryand leavesdepends_on: condition: service_healthystuck.
Fail-closed behavior
The governed path is fail-closed. Under enforcement_mode="enforce" a
gateway deny blocks the tool call before it runs. Critically, an unreachable
dependency is treated as a deny, not as an allow: if the agent cannot reach
its runtime sidecar — or the runtime cannot reach the gateway — the SDK denies
the action rather than letting it execute ungoverned. This is why the uid
alignment and endpoint wiring above matter: a mis-wired socket or gateway
endpoint surfaces as blocked calls, not as silent bypass.
Where a specific SDK’s fail-closed-on-unreachable default is still being finalized, that is tracked under AAASM-4766 / AAASM-4760; the enforcement contract documented here is the intended behavior of the governed topology.
A complete, genuine end-to-end version of this topology — real SDK, real
runtime, real gateway, a policy that actually blocks a delete_file call — is
maintained in the examples repository at
scenarios/live-core-enforcement/.
Provenance & verification
Every published image is built multi-arch with SLSA build provenance attached as an in-toto attestation, so you can verify how and from where an image was built before you run it.
Inspect the provenance of an image (and its multi-arch manifest) with BuildKit’s imagetools:
docker buildx imagetools inspect \
ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6 \
--format '{{ json .Provenance }}'
Pin by immutable digest. Tags (even release tags) are convenient but a digest is content-addressed and cannot be moved. Resolve the current digest:
docker buildx imagetools inspect ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6
# Name: ghcr.io/ai-agent-assembly/aa-gateway:v0.0.1-rc.6
# Digest: sha256:9754356e4d9b6062b0bc3b40fde62c196112636ee11650fb6e4cee91ae428954
…then pin it in your docker run / Compose / Dockerfile:
ghcr.io/ai-agent-assembly/aa-gateway@sha256:9754356e4d9b6062b0bc3b40fde62c196112636ee11650fb6e4cee91ae428954
ghcr.io/ai-agent-assembly/aa-runtime@sha256:5b604612b80f14a1763cc50d126a31ee05454af874bd8a4c3d3cf6fe6c444b74
(The digests above are the v0.0.1-rc.6 manifests at the time of writing —
always re-resolve for the version you intend to run.)
Non-root posture. The aa-gateway and aa-runtime images are built on
gcr.io/distroless/static:nonroot — they contain only the statically linked
binary (no shell, no package manager) and run as uid 65532 by default,
minimizing attack surface.
SBOM and image signatures are a tracked follow-up (AAASM-4762). Once published, this section will document verifying the SBOM attestation and the image signature; until then, provenance inspection and digest pinning above are the available verification steps.
Support boundary
The container images on this page stand up the limited-function OSS
self-host stack — the enforcement data plane (runtime + gateway + optionally
aa-proxy, built from source) — for local evaluation and development. You are
responsible for operating and monitoring what you self-host.
Full functionality is SaaS-only: team budgets, the central agent registry, the operator dashboard, persistence, identity federation (SSO/SCIM), regional data residency, and tamper-evident audit are intended for the managed AI Agent Assembly cloud — planned, not available. See Open core boundary for the precise line between what you can self-host and what the SaaS adds.
Related documentation
- Self-Host Observability — health, readiness, and Prometheus metrics for the images above.
- Open core boundary — OSS vs SaaS feature split and crate licensing.
- Managed control plane (design preview) — the managed SaaS platform.
- Security model — the interception mechanisms and defense-in-depth posture.
- Troubleshooting — common issues when running the stack.
Last reviewed: 2026-07-18 · AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Self-host observability
This page is for operators and SREs running the limited-function OSS stack — the self-hostable Apache-2.0 crates you can bring up locally (via the published Docker Compose example) for evaluation and development. It answers the operator’s first question — “can I monitor what I run?” — by showing where the shipped binaries expose their liveness/readiness probes and their Prometheus metrics, so you can wire up a health check and a scrape target without reading the Rust source.
Scope: this is the limited-function self-host stack. There is no availability, on-call, or compliance commitment from anyone — not for a stack you self-host, and not for the managed control plane, which is planned and not available. Self-hosting is intended for local evaluation and development; you are responsible for operating and monitoring it. This page documents the observability surface the OSS binaries already expose; it is not a production deployment or orchestration guide (no Helm / Terraform / Kubernetes).
What the stack exposes
The self-hostable stack runs two binaries with a published container image — aa-runtime and aa-gateway (see Docker & Containers). They do not expose the same surface: only aa-runtime serves HTTP health/metrics (on :8080). As launched in the container topology (--policy … --listen 0.0.0.0:50051, no --mode), the gateway runs in legacy gRPC mode and serves gRPC only on :50051 — it exposes no HTTP /healthz and no /api/v1/health. Those HTTP surfaces exist only when the gateway is started in a different mode: /healthz in local or remote mode, and /api/v1/health in local mode only (--mode local, a single-process dev topology this container stack does not use, and which has no separately runnable aa-api container).
| Component | Surface | Default endpoint(s) | Purpose |
|---|---|---|---|
aa-runtime | Health + metrics HTTP server | /health, /ready, /metrics on AA_METRICS_ADDR (default 0.0.0.0:8080) | Liveness, readiness, and the Prometheus scrape target |
aa-gateway (legacy gRPC mode — the container topology) | TCP liveness | gRPC port :50051 | Process-liveness via a TCP connect; no HTTP health endpoint is served in this mode (grpc.health.v1.Health tracked in AAASM-4759) |
The gateway’s HTTP /healthz and /api/v1/health surfaces are not part of the container topology on this page — they appear only when the gateway is launched in a non-default mode, summarized below:
| Surface | Where it exists | Default endpoint |
|---|---|---|
/healthz | gateway in local or remote mode | /healthz |
/api/v1/health | gateway in local mode only (--mode local) | /api/v1/health |
The rest of this page covers each surface and gives copy-paste probe and scrape examples.
Health and readiness probes
aa-runtime — /health and /ready
The runtime runs a combined health/metrics HTTP server bound to AA_METRICS_ADDR (see Metrics endpoint below for the env var and its default). It serves two probe routes:
GET /health— liveness. Returns200 OKwith a JSON body reportingstatus, process uptime, events processed, and which enforcement layers are active or degraded. Use this as a liveness probe.GET /ready— readiness. Returns200 OK(bodyready) once the runtime is ready to accept work, or503 Service Unavailable(bodynot ready) before then. Use this as a readiness/startup gate.
$ curl -fsS http://localhost:8080/health
{"status":"healthy","uptime_secs":42, ...}
$ curl -fsS http://localhost:8080/ready
ready
aa-gateway — TCP liveness on :50051
In the container topology the gateway runs in legacy gRPC mode (--policy … --listen 0.0.0.0:50051, no --mode). In that mode it serves gRPC only and exposes no HTTP health endpoint — there is no /healthz to curl. Gate its liveness with a TCP connect to the gRPC port :50051 from the host (the aa-gateway image is distroless, so an in-container CMD-SHELL probe cannot run either — see Docker & Containers).
# TCP-level liveness — succeeds once the gateway is accepting gRPC connections.
$ nc -z localhost 50051 && echo "gateway up"
gateway up
A standard gRPC grpc.health.v1.Health service on :50051 is being added (AAASM-4759); until it ships, the TCP check above is the gateway liveness signal for the container topology.
HTTP health (/healthz, /api/v1/health) — local/remote mode only
The gateway does serve HTTP health endpoints, but only when launched in a non-default mode — not in the legacy-gRPC container topology on this page:
GET /healthz— process-liveness in local and remote mode. Returns200 OKwith a small JSON body (e.g.{"mode":"local","version":"...","storage":"sqlite","uptime_secs":...}).GET /api/v1/health— REST API health in local mode only (--mode local), mounted by the sameaa-gatewayprocess (there is no separateaa-apicontainer). Returns200 OKwhen every subsystem check passes, or503 Service Unavailablewhen any is degraded; the JSON body includes the buildversion,api_version, uptime, and achecksmap for the policy engine, registry, audit, and alerts.
Local mode is a single-process dev topology, not the gateway + runtime container stack documented in Docker & Containers; its HTTP port comes from that mode’s own configuration rather than the --listen gRPC address.
Prometheus metrics endpoint
The aa-runtime health/metrics server exposes a Prometheus text-format scrape endpoint.
| Setting | Value |
|---|---|
| Env var | AA_METRICS_ADDR |
| Default bind address | 0.0.0.0:8080 |
| Metrics path | /metrics |
| Scrape target | http://<runtime-host>:8080/metrics (with the default bind address) |
AA_METRICS_ADDR is the single environment variable that controls this server’s bind address; the same server serves /health, /ready, and /metrics. Set it to change the interface or port, e.g. AA_METRICS_ADDR=127.0.0.1:9090 to bind loopback only. (0.0.0.0 is a bind address — point your scraper at a routable host/IP for the runtime, not at 0.0.0.0.)
$ curl -fsS http://localhost:8080/metrics
# Prometheus text exposition format
aa_events_received_total 0
aa_events_emitted_total 0
...
Baseline metrics
The runtime pre-registers six baseline metrics at 0 on startup, so the /metrics surface is stable from the very first scrape (a metric never “appears late” the first time it is incremented). The names and types below are taken directly from the runtime source; the “What it represents” column is explanatory (the source registers names and types only, without HELP text). Additional metrics may appear as the runtime does work.
| Metric | Type | What it represents |
|---|---|---|
aa_events_received_total | counter | Governance events the runtime has received |
aa_events_emitted_total | counter | Events the runtime has emitted downstream |
aa_policy_violations_total | counter | Policy violations observed |
aa_policy_evaluations_total | counter | Policy evaluations performed (currently reports 0; reserved for a forthcoming release) |
aa_active_connections | gauge | Currently active connections |
aa_channel_utilization_ratio | gauge | Internal channel utilization ratio |
Note: these six are the baseline surface. Only
aa_active_connectionsandaa_channel_utilization_ratioare gauges; the other four are counters. None are histograms. Because they start at0, an all-zero scrape shortly after startup is expected, not a sign of a broken exporter.
Minimal scrape configuration
Point a Prometheus server at the runtime’s metrics endpoint. A minimal prometheus.yml scrape job:
scrape_configs:
- job_name: aa-runtime
metrics_path: /metrics
static_configs:
- targets: ["<runtime-host>:8080"] # matches AA_METRICS_ADDR's port
Replace <runtime-host> with the address where the runtime is reachable (for the Docker Compose example, the runtime service’s name/port on the compose network). If you override AA_METRICS_ADDR, update the target port to match.
For a liveness/health check outside Prometheus, probe the runtime’s /health over HTTP (curl -f gates on its non-200 status) and check the gateway with a TCP connect to :50051 (nc -z) — in the container topology the gateway serves no HTTP health endpoint. The HTTP /healthz / /api/v1/health probes apply only if you run the gateway in local/remote mode, as noted above.
Where to confirm these details
These endpoints live in the Apache-2.0 crates in the agent-assembly repository, so you can verify them against the source you run:
aa-runtime/src/config.rs—AA_METRICS_ADDRand its default.aa-runtime/src/runtime.rsandaa-runtime/src/health/— the health/metrics server and the baseline metrics.aa-gateway/src/main.rs— the mode resolver (resolve_mode); the default islegacy-grpc, which runsserve_tcp(gRPC only, no HTTP health) unless--mode/AA_MODEselectslocalorremote.aa-gateway/src/routes/healthz.rs— the/healthzliveness probe (local and remote modes only).aa-api/src/routes/health.rs— the/api/v1/healthcheck (mounted by the gateway in local mode; there is no separately runnableaa-apicontainer).
Related documentation
- Open core boundary — what is in the Apache-2.0 core today vs. intended for the commercial tier.
- Managed control plane (design preview) — the managed control plane, which is planned and not available.
- Security model — the Telemetry layer and the broader defense-in-depth posture.
- Troubleshooting — common issues when running the stack.
Last reviewed: 2026-07-18 · AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Troubleshooting
This page is a starting point when something is not working the way the docs describe. It is for operators and developers who have already read the Docker Compose quickstart or an SDK page and hit a specific problem.
Because AI Agent Assembly is composed of five independently versioned programs (core, three SDKs, and Arena), most concrete runtime symptoms are covered in the owning component’s own troubleshooting section. This page routes you to the right one and lists cross-cutting issues that don’t belong to a single component.
Common first-run failures
If you are evaluating AI Agent Assembly for the first time, these are the symptoms newcomers hit most often — each with the exact fix. If your symptom isn’t here, use the component routing table further down.
| Symptom you see | Cause | Fix |
|---|---|---|
pip install agent-assembly → ERROR: Could not find a version that satisfies the requirement agent-assembly / No matching distribution found | Only pre-release versions are published on PyPI right now; pip skips pre-releases by default. | Install with the --pre flag: pip install --pre agent-assembly. |
| On Python 3.13 / 3.14, agent registration appears to do nothing — no events reach the gateway | Older SDK builds had no wheels for CPython 3.13/3.14, so the native extension silently fell back to a no-op. | Upgrade to rc.4 or later, which ships cp313 and cp314 wheels: pip install --pre --upgrade agent-assembly. Confirm your interpreter with python --version. |
| SDK cannot reach the gateway even though a gateway is running | Connecting to the wrong port/protocol — the SDKs speak gRPC on 50051, while the gateway’s REST/OpenAPI control-plane surface (used by the dashboard, and what AA_GATEWAY_URL points at) is HTTP on 7391. Port 8080 is a different endpoint — the aa-runtime health/metrics server (AA_METRICS_ADDR) — not the gateway REST API. | Point the SDK at the gRPC endpoint (50051), not the REST port (7391) or the metrics port (8080). For a fully local loop with no external gateway, run one yourself: aasm start brings up a local gateway the SDK can register against. |
aa-runtime logs policy file not found — starting without enforcement | Neither AA_POLICY_PATH (local enforcement) nor AA_GATEWAY_ENDPOINT (delegate to a gateway) is set on the runtime container, so it has no policy source at all — every governed call is allowed by default (AAASM-4988). | Set one of the two on the aa-runtime service: AA_GATEWAY_ENDPOINT to delegate per-tool decisions to a real aa-gateway (see the governed topology), or AA_POLICY_PATH for the runtime’s own coarse, action-type local policy. |
ImportError / ModuleNotFoundError for AgentExecutor or create_react_agent when running a LangChain example | Recent LangChain moved these legacy agent constructors out of the top-level langchain package into the langchain_classic package. | Import from the new location: from langchain_classic.agents import AgentExecutor, create_react_agent (and pip install langchain-classic if it isn’t already present). |
Where component-specific troubleshooting lives
| Symptom | Look here |
|---|---|
| SDK cannot register the agent / handshake fails | Python SDK docs, Node SDK docs, Go SDK docs |
Sidecar proxy (aa-proxy) drops connections | Core docs — Proxy section |
eBPF sensor (aa-ebpf) fails to load | Core docs — eBPF section |
| Policy YAML rejected at gateway | Policy reference |
| Compatibility mismatch between core and an SDK | Compatibility matrix |
Cross-cutting checks
Before opening an issue, verify:
- The SDK version matches a supported core version — see the Compatibility matrix.
- The API key is set in the environment (
AA_API_KEY) — an unset credential is the most common cause of “agent registration failed”. - The gateway URL is reachable from the host running the agent
(
AA_GATEWAY_URL, defaulting tohttp://localhost:7391, a local gateway auto-started viaaasm startif none is already running).
Still stuck? Open an issue
If none of the routes above resolve the problem, open a GitHub issue on the owning repository — the core team monitors each repo’s issue tracker and routes docs bugs back here.
Include the SDK version, core version, and a minimal reproducer. Docs bugs can be filed on this repo's tracker instead.
Last reviewed: 2026-07-17 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Check a published claim
This section routes a published sentence to the evidence behind it, and routes a vulnerability to the people who can act on it; it holds no evidence of its own, because a copy of evidence is not evidence.
Take a sentence to its evidence, in three steps
1. Find the claim term
A statement about what happened to an action is incomplete without its timing and its failure posture. ADR 0033 §6 defines the canonical vocabulary for this, and downstream material is required to pick one of its terms rather than an undifferentiated verb.
The eleven terms are:
Observed · Detected · Evaluated · Denied before execution · Redacted · Approval required · Degraded · Unmeasured · Experimental · Planned · Unsupported
Two of those words appear on this site on a different axis. Planned and Experimental above are §6 terms about an action. The
🗺️ Plannedyou may have seen in the sidebar beside a page title is a documentation-area label from the status map, about an area of this documentation. A page is🗺️ Plannedbecause its area is; an action isPlannedin §6’s sense. Neither licenses a conclusion about the other, and when you are checking a sentence it is the §6 sense that applies.
What each one means, and which mechanism can legitimately reach it today, are defined in ADR 0033 §6. This page names the terms so you can find them; it does not define them, because a second definition is how two definitions start disagreeing.
If a sentence uses none of these terms, that is the finding.
2. Ask for the evidence that term requires
Each term in §6 carries the evidence that substantiates it, and they are not interchangeable: a durable event attributed to an action substantiates Observed, and does not substantiate Denied before execution, which needs a refusal by a component sitting before the effect.
Three signals look like coverage and are not, and ADR 0033 §7 names them so they cannot
be offered as substantiation: an environment variable that replaces a probe result, a
probe satisfied by a binary being present on $PATH, and a capability bit asserted
unconditionally.
3. Check the completeness claim separately
A statement usually carries two claims at once: what happened to an action, and how much was covered. They have different owners and different evidence, so they are checked separately and the more restrictive published outcome governs.
| To check | Read |
|---|---|
| What a scenario does and does not demonstrate | Risk scenarios, and its negative control |
| Wording that has already been reviewed for reuse | Risk scenarios: approved wording |
| What may be said about the managed service | SaaS claim publication checklist |
| Which areas are open source and which are not | Open core boundary |
| Who owns an area, and how mature its documentation is | Status map |
Report a vulnerability
Do not report a security issue through a public GitHub issue.
Vulnerability reports go to the repository that owns the affected code, through
GitHub’s private vulnerability reporting on that repository, and each repository’s
SECURITY.md carries its current reporting route and disclosure policy:
| Component | Security policy |
|---|---|
| Core | agent-assembly/SECURITY.md |
| Python SDK | python-sdk/SECURITY.md |
| Node SDK | node-sdk/SECURITY.md |
| Go SDK | no repository policy today; use the organisation policy |
| Arena | no repository policy today; use the organisation policy |
| This documentation hub | no repository policy today; use the organisation policy |
The organisation-wide security policy is the fallback for any repository that does not publish its own, and the rows above were checked rather than assumed.
If you are unsure which repository owns the code, file against core so it can be routed.
The reporting address is deliberately not repeated on this page. Each SECURITY.md
above is the canonical source for its own route, and a copied address is one that keeps
working right up until it does not.
What this page does not do
- It does not hold evidence. It routes to the artifact that does.
- It does not define a claim term. ADR 0033 §6 does.
- It does not decide whether a statement may be published. For managed-service wording that is the SaaS claim publication checklist.
Going deeper
The claim vocabulary, the evidence each term requires, and the mechanisms mapped onto them are in ADR 0033.
Last updated: 2026-08-13 by AI Agent Assembly Team
SaaS claim publication checklist
This page is the control that keeps the managed-service claims off this hub until they are true. It exists for two audiences: readers who want to know why the SaaS pages are almost empty, and maintainers who are about to add a managed-service claim back.
⚠️ Provisional. This page was authored alongside the removals it records, ahead of the canonical claim-governance ADR (AAASM-5621) that will define the approval semantics, owner roles, and evidence grades this org actually uses. Where this page and that ADR disagree, the ADR wins and this page gets rewritten to match. It is expected to become an adoption record feeding the capability/evidence manifest (AAASM-5531) rather than a standalone registry. Treat the register below as an accurate record of what was removed and why — that part is evidence — and the approval workflow around it as a placeholder.
The rule it enforces is narrow and absolute in one direction only:
A managed-service operational or contractual claim may not be published on this hub until the service exists, a named owner has approved the claim, and the evidence listed below has been produced.
“Removed rather than softened” is deliberate. Rewording “99.9% uptime SLA” into “high availability” does not reduce the commitment a reader takes away from it; it only makes the commitment harder to audit. Unevidenced claims are deleted.
Scope
This checklist covers claims about the managed AI Agent Assembly service: its availability, plans, quotas, regions, onboarding, billing, support, and legal or compliance posture.
It does not cover the open-source stack. Apache-2.0 behaviour is documented normally, in core docs, the policy reference, the security model, and Docker & containers.
How to use it
- Find the claim class you want to publish in the register below.
- Produce the evidence in the Evidence required column. Link it from the pull request.
- Get written approval from the role in the Approval owner column. An approval covers a specific wording, not a topic.
- Publish the claim with the wording that was approved, and add the label the claim’s maturity warrants — see Source of truth & status for the label vocabulary this hub uses.
- Update the register row so the next maintainer can see what was approved and on what basis.
If a claim is not in the register, it does not get a fast path — add a row for it first.
Register of removed claims
Removed by AAASM-5612 on 2026-08-06 from Managed SaaS onboarding (design preview), Managed control plane (design preview), Open core boundary, and Security model.
The security-model rows differ from the rest in one way worth noting: several of those claims were not merely unevidenced, they were contradicted by the Apache-2.0 source. Where that is the case the row says so, because the evidence needed to restore such a claim is a code change, not an approval.
Approval owners are roles, not individuals, so the register does not go stale when people change. “Evidence required” is the minimum; an owner may ask for more.
Availability and service commitments
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| Named uptime or availability percentages (for example a 99.5% or 99.9% monthly figure), per tier | Asserted a measured, contractual availability level for a service that is not running, so no availability has ever been measured | Head of Engineering and Legal | A production service carrying real traffic; a published measurement window with the method stated; a public status page; and the availability commitment written into published terms |
| Service credits for missed availability | A financial remedy implies a contract; none is published | Legal and Finance | Published terms of service containing the credit schedule, countersigned |
| Support response times (for example 24-hour business-hours or 4-hour any-time response) | Asserted a staffed response commitment with no on-call rota, ticket system, or measurement behind it | Head of Support and Legal | A staffed support function with a ticketing system; a measured response-time distribution over a stated period; the commitment written into published terms |
| A named support channel presented as operating (support portal, community forum) | The referenced endpoints were not serving | Head of Support | The channel reachable at a published URL, with a named owner and a stated scope |
| A dedicated named engineering or SRE contact per customer | A staffing commitment with no staffing model behind it | Head of Engineering and Head of Support | A defined role with allocated headcount, and the commitment written into published terms |
Plans, quotas, and pricing
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| Plan and tier names presented as purchasable, and what each plan includes | Presented a commercial catalogue that cannot be bought | Product Lead and Finance | An approved and published pricing page; a working purchase path; the plan-to-entitlement mapping enforced by the running service |
| Prices, currencies, and billing periods | No published price list exists | Finance and Legal | An approved price list and published terms covering it |
| Numeric quotas — maximum agents, maximum policies, retention periods per plan | Published numbers that the service does not enforce | Product Lead and Head of Engineering | The limit enforced by the running service, and a test demonstrating the enforced value matches the documented one |
| “Unlimited” for any resource | An unqualified absolute; every real system has a limit | Product Lead and Head of Engineering | Either a stated numeric limit, or an explicit statement of what bounds the resource in practice |
Regions and data residency
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| A list of available regions, and per-region locations | Presented a deployment footprint that does not exist | Head of Infrastructure | The region running and serving traffic; the location published; the region selectable through a working path |
| Data-residency guarantees (“data at rest and in transit stays within the selected region”) | A data-protection guarantee with no deployment, no control, and no audit behind it | Head of Infrastructure and Legal | A technical control enforcing the boundary; an audit demonstrating no cross-region egress of customer data; the guarantee written into published terms |
| Dedicated single-tenant regions | An isolation guarantee with no isolation implementation | Head of Infrastructure and Legal | The isolation model documented and independently reviewed; the guarantee written into published terms |
| Region migration on request | An operational procedure implying an operations team and a runbook | Head of Infrastructure | A tested migration runbook, and a named team accountable for running it |
Onboarding and account operations
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| Signup and contact-sales URLs presented as working | The referenced endpoints were not serving | Product Lead | The URL returning the described page in production |
| Step-by-step console instructions (navigation paths, screen and button names, credential-issuance screens) | Instructed readers to use screens that are not reachable, and the described paths did not match the intended product | Product Lead and Design | The console reachable in production, and the documented navigation path verified against the shipped UI on the day of publication |
| A tenant or workspace identifier format | Published a specific string format that the service does not produce | Head of Engineering | The format emitted by the running service, and a test asserting it |
| Onboarding-duration estimates (“about 10 minutes”, “about 30 minutes”) | An unmeasured performance claim | Product Lead | A measurement across real onboardings, with the sample size and method stated |
| Procurement timelines (“1–3 weeks”, week-by-week activity tables) | Described a sales and legal process that is not running | Head of Sales and Legal | A defined procurement process with a named owner, and observed durations across completed deals |
Billing
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| A named payment processor and card-billing instructions | Named a specific processor that is not integrated, and instructed readers to enter card details on a page that does not exist | Finance and Head of Engineering | The processor integrated in production; a completed test transaction; the instructions verified against the shipped flow |
| Invoicing behaviour — issue cadence, recipients, delivery | Described an invoicing operation that does not run | Finance | Invoices issued in production, and a named owner for the billing operation |
| Payment terms (for example net-30), purchase orders, wire or ACH acceptance | Contractual payment terms with no published contract | Finance and Legal | Published terms containing the payment terms, countersigned |
| Payment-method management instructions | Described console screens that are not reachable | Finance and Product Lead | The flow reachable in production and verified on the day of publication |
Identity, provisioning, and access control
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| SSO configuration walkthroughs (SAML 2.0 and OIDC), including console paths, endpoints, and attribute mappings | Instructed readers through screens that are not reachable, for protocol support that must be confirmed protocol by protocol rather than asserted as a pair | Head of Engineering and Product Lead | The specific protocol working in production against at least one named identity provider; the walkthrough verified end to end against the shipped UI; each protocol documented only once it individually works |
| SCIM 2.0 provisioning instructions and a supported-operation matrix | Published a per-operation support matrix that was not verified operation by operation | Head of Engineering and Product Lead | Each listed operation exercised against the running service, with the test as evidence; unsupported operations shown as unsupported rather than omitted |
| A named role model and its per-role permission table | Published role names and permissions that did not match the intended product | Head of Engineering and Product Lead | The role set and permissions read from the running service, with authorization tests as evidence |
| Group-to-role mapping instructions | Described configuration screens that are not reachable | Head of Engineering | The mapping configurable in production and verified end to end |
Audit and security posture
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| “Tamper-evident” or “immutable” audit log, unqualified | A named mechanism does exist — an unkeyed SHA-256 hash chain over the JSONL audit file, checkable with aasm audit verify-chain. (A shipped gateway writes one fixed gateway-default.jsonl, not per-session files.) But unqualified, the phrase claims more than it covers: the chain is unkeyed (an actor who can rewrite the file can re-chain it), it does not extend to the database mirror, and it says nothing about completeness | Head of Security and Head of Engineering | For the JSONL scope the mechanism may be named with those three bounds stated in the same sentence. Dropping the bounds requires a keyed construction, chain metadata persisted in the DB, and an independent review |
| Audit chain coverage stated without naming the sink | The chain covers the JSONL files only; the audit_events and audit_logs tables carry no seq, previous_hash, or entry_hash because the runtime-to-storage conversion drops them | Head of Engineering | Chain metadata persisted alongside the DB rows, plus a verifier that runs against the table |
| Audit entries “signed with HMAC-SHA256 using a log-signing key” | Contradicted by the source — the mechanism is a keyless SHA-256 hash chain; no HMAC over audit records and no log-signing key exist. A keyless chain does not resist an actor who can rewrite the store and recompute it | Head of Security and Head of Engineering | A keyed construction actually implemented, with the key’s custody model documented — this needs a code change, not an approval |
| “Logs are append-only; no delete or update API exists” | Contradicted by the source — retention pruning issues DELETE FROM audit_events in both the SQLite and Postgres drivers, and no trigger, revoked grant, or WORM setting prevents deletion | Head of Engineering | An enforced constraint at the storage layer, plus a test demonstrating that a delete or update against audit rows is rejected |
| “Every agent action produces a log entry” | Contradicted by the source — emission is fire-and-forget onto a bounded channel: on backpressure the entry is dropped, counted, and the action proceeds. Separately, budget debits emit no audit entry at all — the budget event types are never constructed | Head of Engineering | A fail-closed emission path (the action is rejected when the audit write cannot be durably accepted), an emitter for every event type the claim covers, and tests for the backpressure and restart cases |
| Audit coverage listed by event category (for example “policy checks, events, and budget debits are audited”) | A category list is a completeness claim per category. Budget debits are currently in the schema but never emitted, so listing them was wrong even though the other two categories were right | Head of Engineering | Each listed category exercised end to end, with a test asserting an entry is persisted for it |
| Configurable audit-log retention, and retention periods per plan | Published retention durations that the service does not enforce per plan | Head of Engineering and Product Lead | The retention period enforced by the running service per plan, with a test asserting it |
| CEF export, and “SIEM integration” as a managed capability | CEF does not exist anywhere in the codebase, and there is no integration — only a file a SIEM could ingest. CSV, JSON and JSON Lines export do ship via aasm audit export, and must not be swept up in this row | Head of Engineering | For CEF: the format actually emitted, plus a sample accepted by at least one named SIEM. For “integration”: a delivery path the product operates, not an export a human runs |
| Console budget configuration presented as a managed-service capability | The described form did not exist, and the fields did not match the budget schema the gateway enforces — see Policy reference | Head of Engineering and Product Lead | The managed configuration path working in production, and its fields reconciled against the enforced policy schema |
Legal and compliance
| Claim removed | Why it was removed | Approval owner | Evidence required to restore |
|---|---|---|---|
| Compliance certifications and frameworks named in an onboarding context (SOC 2, HIPAA, GDPR, ISO 27001) | Named certifications in a way that implied the service holds them, or is ready to be assessed against them | Legal and Head of Security | The completed audit report or certificate from the assessing body, with its scope and date; the claim restated to match that scope exactly |
| A compliance status table with a target date (“SOC 2 Type II — In preparation, target Q3 2026”; “ISO 27001 — Roadmap”) | A status table inside a compliance section reads as a programme with a trajectory, and the date makes it a commitment. No audit report, assessment scope, or engagement backed any row | Legal and Head of Security | A signed engagement with the assessing body defining scope and timing, before any date is published; the certificate itself before any status beyond “engaged” is published |
An export flag or metadata header named after a framework (for example --compliance soc2) cited as compliance evidence | A formatting feature that prepends a header is not an attestation, and citing it as one inflates a build flag into a certification | Head of Security | Nothing to restore — the flag may be documented as a formatting option, but never as evidence of compliance |
| Availability of a Data Processing Agreement (DPA) | Asserted that a specific legal instrument exists and can be requested | Legal | The executed template, approved by counsel, and a named owner for the request process |
| Availability of a Business Associate Agreement (BAA) | Asserted a HIPAA-specific legal instrument, which additionally presupposes a compliance posture that has not been assessed | Legal and Head of Security | The executed template approved by counsel, and the underlying compliance evidence the agreement depends on |
| Countersignature and legal-review workflow descriptions | Described a legal operation that is not running | Legal | A defined process with a named owner |
| A named commercial licence for the non-open-source capabilities | Named a licence whose terms are not published anywhere | Legal | The licence text published, with a version and effective date |
| A “legal approver” attribution in a page footer | Attributed legal sign-off to a page whose content had since changed | Legal | Sign-off recorded against a specific page revision, re-obtained whenever that page’s claims change |
Language rules that apply to every row
These apply even after an owner approves a claim.
- Do not use the present tense for a capability that is not running. “Supports X” and “is available in X” are present-tense claims.
- Label the maturity. Use the label vocabulary in Source of truth & status. An unlabelled statement reads as shipped.
- Do not use unqualified absolutes — “all”, “every”, “complete”, “comprehensive”, “universal”, “unlimited”, “immutable”, “cannot be bypassed”. If one is genuinely correct, name the boundary it holds within and the evidence for it, in the same sentence.
- Do not publish a number you have not measured. Latencies, durations, retention periods, and quotas are measurements, not illustrations.
- Do not soften instead of removing. If the evidence is missing, the claim comes out.
- Check the claim against the code, not against the neighbouring prose. Several claims removed here were restated across three or four pages, and two contradicted the Apache-2.0 source outright. A claim that agrees with another doc is not thereby verified.
- Ask whether the mechanism exists and whether a released binary can reach it. Distinct from “is it worded correctly”, “does it hold once enabled” and “is it on by default”. A capability present in the source but unreachable in a shipped build — a sink hardcoded off, a daemon excluded from the release artifacts, a per-session filename never given a session — reads to a reader as a feature they have. Several claims here passed every other check and failed this one.
- Understating is also inaccurate. The goal is a claim that matches the system, not the smallest claim available. During this pass one control was initially written as weaker than it is — the audit chain was described as unverifiable when an operator command to verify it ships today. Removing an unevidenced claim and erasing a real one are different acts; only the first is the safe default.
- Name the scope a control actually covers. A mechanism that protects one sink, one protocol, or one code path should say which. “The audit log is hash-chained” and “the JSONL audit files are hash-chained, the database mirror is not” have very different operational consequences.
Related documentation
- Source of truth & status — the canonical maturity label for every area of this hub
- Managed SaaS onboarding (design preview) — managed onboarding, planned
- Managed control plane (design preview) — the managed control plane, planned
- Open core boundary — the open-source / commercial split
- Security model — the security posture of the open-source enforcement path, including the audit log’s actual integrity properties
Last reviewed: 2026-08-06 · AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Policy Reference
The field-by-field policy reference is maintained in Core, generated from the
same schema aa-gateway validates against:
Core policy reference.
This page is a summary and a router to it — it does not restate field types,
defaults, or validation rules, so it cannot drift out of sync with the schema
the way a second hand-written copy can.
What a policy document is
A policy is a YAML document that scopes what an agent may do — which domains it can reach, which tools it can call, how much it can spend, and more. The gateway evaluates it and returns an allow, deny, or rate-limit decision; whether that decision is enforced before the action runs depends on which path the request took — see Security model for the per-path posture.
Where each block is documented
| Block | What it controls | Core section |
|---|---|---|
| Document formats | Envelope vs. flat YAML | Document formats |
Top-level fields, scope | Which agents a policy applies to, cascade order | Top-level fields |
network | Outbound domain allowlisting | network |
schedule | Active-hours time windows | schedule |
budget | Spend caps, currency, reset behaviour | budget |
data | Sensitive-data detection and redaction | data |
tools | Per-tool allow/deny/approval and rate limits | tools |
capabilities | Coarse-grained capability grants | capabilities |
approval | Escalation overrides | approval |
Core also documents filesystem and syscalls blocks (Linux host-level
scope) that this hub does not summarise separately — see
filesystem
and syscalls
in the Core reference.
What this page does not cover
Field types, defaults, validation rules, and enumerated valid values live only in the Core reference above — restating them here is exactly the duplication that let this page fall out of sync with ADR 0033 §2/§4 on when a policy decision actually binds before execution. If you need a field’s type or default, follow the link.
Going deeper
For worked example policies (minimal budget-only, network allowlist, capability control, rate-limiting with approval, business-hours schedule, PII detection, and a full policy exercising every section), see Core’s example policies.
Related documentation
- Security model — IronClaw layers and policy engine position in the stack
- Managed control plane — design preview — planned, not available
- Managed SaaS onboarding — design preview — planned, not available
Last updated: 2026-09-07 by AI Agent Assembly Team
Capability & protection status
This page is generated, row for row, from
governance/capability-manifest.yaml
in the agent-assembly monorepo — ADR 0034’s layer T2, the strongest layer any page
on this hub (T5) may draw from. It does not restate or interpret those rows in prose;
it renders them, so nothing here can broaden what the manifest itself claims (ADR 0034
Decision 2).
Every row is shown, including rows carrying Unsupported or Unmeasured coverage and
Not applicable or Not measured protection state — those are the manifest’s own
answer for that capability, not an omission.
What the columns mean
- Coverage — behaviour on evidence, ADR 0033 §6’s closed eleven-term vocabulary
(
Observed,Detected,Evaluated,Denied before execution,Redacted,Approval required,Degraded,Unmeasured,Experimental,Planned,Unsupported). One action, one host, one time. - Protection state — ADR 0030 §4.1’s integration ladder for one dev-tool
integration on one host: whether agent-assembly is installed, integrated and
enforcing there at all. Distinct from coverage — a row can be
Integratedand still carry no coverage evidence; see the manifest’s owngovernance/README.md“Three axes, three owners” section. - Released channels / Platform — where the artifact that delivers this row’s capability is actually obtained, and on which platform families. Never a promise about what happens once installed — that’s coverage and protection state.
These three are never mixed into one cell. A capability can be released everywhere, integrated nowhere, and denied-before-execution on the one platform it does run — each of those is a separate, independently true statement, and folding them together would make one of the three appear to certify the others.
Provenance
- Manifest version:
1.0.0 - Ticket: AAASM-5531
- Fix version: agent-assembly v0.0.1-rc.7
- Extract taken at commit:
e2730ddaf422 - Evidence surveyed at:
299de38830b5(2026-08-06)
The evidence surveyed at commit is not this page’s own commit — it is the point in
agent-assembly’s history the manifest’s rows were last verified against. A row is
only as current as that commit, regardless of when this page itself was last
regenerated.
Capability table
| ID | Capability | Owner | Framework / tool | Platform | Released channels | Coverage | Protection state |
|---|---|---|---|---|---|---|---|
| C1 | Outbound credential scan and redact on an inspected request | aa-security | aa-proxy, aa-security | linux, macos | crates_io, github_release, homebrew, install_script | Redacted | Not applicable |
| C2 | Credential substitution at egress — the real provider key never enters the agent | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Redacted | Not applicable |
| C3 | Credential injection via SecretsService.DispatchTool | aa-api | aa-api, aa-gateway | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| C4 | Model response credential scanning | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| C5 | Environment inheritance by aasm run | aa-cli | aa-cli | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| C6 | Credential scanner recall | aa-security | aa-security | linux, macos, windows | crates_io, github_release, homebrew, install_script | Detected | Not applicable |
| G1 | aa-runtime to gateway unreachable on a policy query | aa-runtime | aa-runtime | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Denied before execution | Not applicable |
| G10 | Audit emission failure | aa-gateway | aa-gateway | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| G11 | Degradation visibility to a user | aa-runtime | aa-api, aa-runtime, dashboard | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| G2 | aa-runtime with no gateway configured, or fail_closed=false | aa-runtime | aa-runtime | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| G3 | aa-proxy to gateway unreachable for MCP adjudication | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| G4 | Credential and DLP default action | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Redacted | Not applicable |
| G5 | SDK cannot reach the aa-runtime UDS socket | SDK fail-closed posture on an unreachable runtime | go-sdk, node-sdk, python-sdk | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Evaluated | Not applicable |
| G6 | eBPF load or attach failure | aa-runtime | aa-ebpf, aa-runtime | linux | crates_io | Degraded | Not applicable |
| G7 | eBPF policy file unreadable or unparseable | aa-runtime | aa-runtime | linux | crates_io | Unmeasured | Not applicable |
| G8 | Gateway policy load or schema failure | aa-gateway | aa-gateway, aa-runtime | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Evaluated | Not applicable |
| G9 | Budget state unreadable or corrupt | aa-gateway | aa-gateway | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| L1 | Claude Code managed launch | aa-devtool-claude-code | claude_code | macos | crates_io, github_release, homebrew, install_script | Denied before execution | Host-enforced |
| L2 | Codex managed launch | aa-devtool-codex | codex | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Integrated |
| L3 | Windsurf managed launch | aa-devtool-windsurf | windsurf | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Integrated |
| L4 | Copilot managed launch | aa-devtool-copilot | github_copilot | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unsupported | Integrated |
| L5 | SaaS / opaque agent | aa-devtool-saas | claude_ai_and_siblings | not_applicable | crates_io, github_release, homebrew, install_script | Observed | Not measured |
| L6 | Unmanaged launch — the user starts the tool directly | none | any | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| L7 | Settings-layer governance surviving an unmanaged launch | aa-devtool-claude-code | claude_code | macos | crates_io, github_release, homebrew, install_script | Unmeasured | Integrated |
| L8 | aasm run –no-proxy | aa-cli | any | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Unmeasured | Not applicable |
| H1 | Shell command / subprocess spawn by a native agent process | none | any | linux, macos, windows | not_applicable | Unmeasured | Not applicable |
| H2 | Shell command intercepted by the eBPF syscall guard | aa-ebpf-probes | any | linux_aarch64, linux_x86_64 | crates_io | Detected | Not applicable |
| H3 | Process exec observation | aa-ebpf-probes | any | linux_aarch64, linux_x86_64 | crates_io | Unmeasured | Not applicable |
| H4 | File read / write / unlink observation | aa-ebpf | any | linux_x86_64 | crates_io | Observed | Not applicable |
| H5 | File access by a WASM-marked tool | aa-sandbox | aa-sandbox | linux, macos, windows | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| H6 | Browser action (Playwright / Selenium / Puppeteer) | none | any | linux, macos, windows | not_applicable | Unmeasured | Not applicable |
| H7 | Database query | none | any | linux, macos, windows | not_applicable | Unmeasured | Not applicable |
| H8 | Shell / file rule declared in a tool’s own settings file | aa-devtool-claude-code | claude_code | macos | crates_io, github_release, homebrew, install_script | Unmeasured | Integrated |
| I1 | Agent identity — Ed25519 did:key with a possession proof | aa-sdk-client | aa-gateway, aa-sdk-client | linux, macos, windows | crates_io, github_release, homebrew, install_script | Evaluated | Not applicable |
| I2 | Transport key for the runtime UDS handshake | aa-sdk-client | aa-runtime, aa-sdk-client | linux, macos | crates_io, github_release, homebrew, install_script | Evaluated | Not applicable |
| I3 | Sub-agent / delegation lineage | aa-core | aa-gateway | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Evaluated | Not applicable |
| I4 | Process-tree identity across fork/exec | aa-ebpf-probes | aa-ebpf, aa-runtime | linux | crates_io | Unmeasured | Not applicable |
| I5 | Tenant / org isolation | aa-gateway | aa-gateway, aa-storage-memory, aa-storage-postgres, aa-storage-sqlite | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Evaluated | Not applicable |
| I6 | Agent attribution of proxy traffic | none | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| I7 | Gateway agent-plane authentication | aa-gateway | aa-gateway | linux, macos, windows | crates_io, ghcr, github_release, homebrew, install_script | Evaluated | Not applicable |
| M1 | MCP tools/call adjudication by the control plane | aa-proxy | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| M10 | MCP-server governance by configuration | aa-devtool-claude-code | claude_code, copilot, windsurf | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unmeasured | Integrated |
| M2 | MCP enforcement with no gateway configured | none | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| M3 | JSON-RPC batch array or malformed envelope carrying tools/call | aa-proxy | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| M4 | Every MCP method other than tools/call | none | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| M5 | MCP over stdio (subprocess pipes) | none | any_mcp_client | linux, macos, windows | not_applicable | Unmeasured | Not applicable |
| M6 | MCP over SSE (text/event-stream) | none | any_mcp_client | linux, macos | not_applicable | Unmeasured | Not applicable |
| M7 | MCP over Streamable HTTP | aa-proxy | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| M8 | MCP over WebSocket | none | any_mcp_client | linux, macos | not_applicable | Unsupported | Not applicable |
| M9 | MCP on a built-in LLM host | none | any_mcp_client | linux, macos | crates_io, github_release, homebrew, install_script | Redacted | Not applicable |
| N1 | CONNECT-time egress allow/deny | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| N10 | Raw TCP that does not speak the proxy protocol | none | any | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| N11 | UDP, QUIC, HTTP/3 | none | any | linux, macos, windows | crates_io, github_release, homebrew, install_script | Unsupported | Not applicable |
| N12 | Local IPC (Unix domain sockets) between third-party processes | none | any | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| N13 | TLS plaintext observation without the proxy | aa-ebpf-probes | aa-ebpf | linux_aarch64, linux_x86_64 | crates_io | Unmeasured | Not applicable |
| N2 | SSRF guard | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| N3 | HTTPS payload inspection and credential DLP on the built-in LLM hosts | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Denied before execution | Not applicable |
| N4 | HTTPS payload inspection on any other host | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Evaluated | Not applicable |
| N5 | HTTPS to a host not under MitM | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| N6 | Model response body scanning on LLM hosts | none | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| N7 | Plain http:// request | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Redacted | Not applicable |
| N8 | HTTP/2, gRPC or WebSocket over a MitM’d host | none | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unsupported | Not applicable |
| N9 | Chunked transfer encoding | aa-proxy | aa-proxy | linux, macos | crates_io, github_release, homebrew, install_script | Unmeasured | Not applicable |
| P1 | Linux x86_64 host-level interception | aa-proxy | aa-ebpf, aa-proxy | linux_x86_64 | crates_io | Experimental | Not applicable |
| P2 | Linux aarch64 host-level interception | aa-proxy | aa-ebpf, aa-proxy | linux_aarch64 | crates_io | Observed | Not applicable |
| P3 | macOS host-level interception | aa-proxy | aa-devtool-claude-code, aa-proxy | macos | crates_io | Unsupported | Integrated |
| P4 | Windows mediation | none | not_applicable | windows | not_applicable | Unsupported | Not installed |
| S1 | Wrapped framework tool call, deny raised before the tool body | python-sdk | google_adk, langchain_handler, mcp_client_session, microsoft_agent_framework, pydantic_ai | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Denied before execution | Not applicable |
| S10 | Direct function call that does not pass a patched seam | none | any | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Unmeasured | Not applicable |
| S11 | Framework with no adapter | none | any_unadapted | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Unmeasured | Not applicable |
| S12 | Raw HTTP, subprocess, filesystem, DB driver, browser automation from inside an SDK-adopting process | none | any | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Unmeasured | Not applicable |
| S13 | The SDK honouring a Deny it received | aa-sdk-client | aa-sdk-client | linux, macos, windows | crates_io, github_release, homebrew, install_script | Evaluated | Not applicable |
| S2 | Wrapped framework tool call, deny returned as a sentinel string | python-sdk | agno, crewai, haystack, llamaindex, openai_agents, smolagents | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Denied before execution | Not applicable |
| S3 | Graph / workflow node execution | python-sdk | langgraph, mastra | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Observed | Not applicable |
| S4 | LangChain tool call via the callback handler | node-sdk | @langchain/core | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Observed | Not applicable |
| S5 | LangChain tool call via the explicit wrapper | node-sdk | @langchain/core | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Denied before execution | Not applicable |
| S6 | Vercel AI SDK / OpenAI Agents tool call | node-sdk | @openai/agents, ai | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Evaluated | Not applicable |
| S7 | Node default mode routes every policy check through an allow-all no-op client | node-sdk | all_node_frameworks | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Unmeasured | Not applicable |
| S8 | Wrapped tool call, Go | go-sdk | any | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Denied before execution | Not applicable |
| S9 | Go default build without -tags aa_ffi_go and CGO | go-sdk | any | linux, macos, windows | crates_io, ghcr, go_modules, npm, pypi | Evaluated | Not applicable |
Unknown capability references
A page elsewhere in this hub that cites a manifest row (a capability_ids entry in
its own metadata block, per Page standards) is checked against
this same manifest by
docs/scripts/validate_capability_ids.py —
a reference to an id that does not resolve to a row here fails CI rather than
publishing silently.
This page is regenerated from the capability manifest, not hand-edited — see Page standards for the metadata block every hub page carries.
Last updated: 2026-08-20 by AI Agent Assembly Team
Glossary
Plain-language definitions of the terms and acronyms used across this documentation. It exists so a first-time or non-specialist reader can decode the security and architecture jargon without leaving the page they are on.
Product concepts
- AI Agent Assembly (AAASM)
- The product this hub documents: a governance layer that sits between your AI agents and the outside world and enforces policy, tracks cost, and intercepts unsafe actions before they run.
- Governance layer
- The one-line description of what AI Agent Assembly is — a control that evaluates and enforces what an agent is allowed to do, rather than only observing what it did after the fact.
- Gateway (
aa-gateway) - The central service that holds the agent registry, evaluates policy, and tracks per-team budgets. Every interception mechanism reports to it.
- Agent
- An autonomous or semi-autonomous program that calls tools, models, or network services on your behalf — the thing AI Agent Assembly governs.
- Policy
- A set of allow / deny / audit rules that decide whether an agent action is permitted. See the Policy reference.
- Policy-as-code
- Expressing those rules as versioned YAML/JSON documents that can be reviewed and deployed through normal Git workflows, instead of clicking through a UI.
- Budget
- A per-team cap on token or dollar spend. When exceeded, the gateway can deny further agent calls.
Interception mechanisms
- SDK layer
- In-process governance: the language SDK wraps your agent’s calls and asks the gateway for a decision. It is advisory — Evaluated, not Denied before execution — since whether a refusal actually holds depends on the calling shim honouring the answer. See Security model.
- Sidecar proxy (
aa-proxy) - A companion process that intercepts an agent’s outbound HTTPS traffic to enforce policy without changing the agent’s code.
- eBPF sensor (
aa-ebpf) - A kernel-level sensor (Linux only) that watches TLS libraries and process syscalls and reports what it sees. Observe-only: it returns no verdict, blocks nothing, and is consulted in no allow/deny decision, so it observes and detects rather than preventing. It is deployed on its own, not as a tier the other mechanisms fall back to.
Security & identity terms
- eBPF (extended Berkeley Packet Filter)
- A Linux kernel technology for safely running small sandboxed programs inside the kernel to observe or filter events, without modifying kernel source.
- uprobe (user-space probe)
- An eBPF hook attached to a function in a user-space library (for example, an SSL library) so the sensor can observe calls at that point.
- Sidecar
- A deployment pattern where a helper process runs alongside your application and handles a cross-cutting concern (here, traffic interception).
- MitM (man-in-the-middle)
- Sitting in the path of a connection to inspect or control it. The proxy performs authorized MitM of an agent’s HTTPS using a per-host certificate authority so it can apply egress policy.
- mTLS (mutual TLS)
- TLS where both the client and the server present certificates, so each side cryptographically verifies the other’s identity.
- STRIDE
- A threat-modeling framework categorizing risks as Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, and Elevation of privilege. Used in the Security model.
- SCIM (System for Cross-domain Identity Management)
- A standard protocol for automatically provisioning and de-provisioning users and groups from your identity provider into an application. 🗺️ Planned — not implemented in AI Agent Assembly. Listed here as a term you will meet in identity tooling, not as a capability that ships.
- SSO / SAML 2.0 / OIDC
- Single sign-on and the two federation protocols (SAML 2.0 and OpenID
Connect) that let operators log in with an enterprise identity provider.
🗺️ Planned — not implemented in AI Agent Assembly. There is no SSO
implementation in
aa-api,aa-gateway, oraa-auth, and no console to sign in to; operators authenticate with an API key or a JWT. Do not plan an IdP integration against it — see Authentication flow. - Ed25519
- A modern public-key signature algorithm. Used here for the one-time possession proof an agent presents at registration — a signature over a server-issued nonce. It is not a reusable bearer credential: subsequent calls carry a random credential token instead. See the Security model.
- AES-256-GCM
- A symmetric authenticated-encryption algorithm. 🗺️ AI Agent Assembly does not use it. This entry previously described it as encrypting stored secrets at rest; there is no AES-256-GCM implementation in the workspace crates, no HSM or KMS integration, and no managed secret vault. Do not treat this stack as a secret store — see Secrets management. The term is retained here only so a reader who met the old claim can find its correction.
- HMAC-SHA256
- A keyed hash. Used here for the REST/admin session JWT, and to verify inbound audit webhooks received from SaaS coding-agent providers — there is no outbound webhook signing path. It is not used on audit-log entries: there is no log-signing key anywhere in the codebase; see Audit log below.
- IronClaw five-layer defense
- The name for AI Agent Assembly’s defense-in-depth model — five security layers (Boundary, Identity, Policy, Vault, Telemetry). These are distinct from the interception mechanisms (SDK, proxy, eBPF), which each live inside the Boundary layer and are deployed independently of one another. The Vault layer is largely aspirational: an in-memory secrets store is mounted but is empty in every shipped build with nothing able to populate it, there is no encryption at rest or key management, and where resolution does succeed the plaintext is returned to the caller. See Secrets management.
- Audit log
- The record of policy decisions and agent-reported events, written to
JSON Lines files, with database tables holding a queryable mirror. A shipped
gateway writes one fixed
gateway-default.jsonl, not per-session files. Four bounds matter and are easy to assume away: the JSONL files are chained with an unkeyed SHA-256 digest (verify withaasm audit verify-chain), so the chain detects casual edits but not an actor who can rewrite the file and re-chain it; the database mirror carries no chain metadata and cannot be verified; the log is append-only by convention, not by an enforced constraint; and emission is best-effort, so an entry can be dropped under backpressure and budget debits produce none at all. Absence of an entry is not proof that an action did not occur. See Audit log for the full statement.
Last updated: 2026-09-07 by AI Agent Assembly Team
Compatibility Matrix
AI Agent Assembly ships as several independently released programs — the core
monorepo (gateway, policy engine, sensors, runtime client) and the Python,
Node, and Go SDKs. Compatibility here means a cross-component contract:
a core release and the SDK release that speaks its wire protocol. An SDK is
compatible with a core release when it is built against — and serializes against —
that core’s protocol contract (aa-proto).
The pairings below are usually 1:1 per release — each row maps one core release
to the SDK release verified to speak its protocol — but a cell may also hold a
version range (e.g. >=0.1.0,<0.2.0 or 0.1.x) when a core release is
compatible with a band of SDK versions rather than one exact tag. Compatibility is
not guaranteed across breaking changes: those are tracked through the
Protocol column. A bump in a row’s protocol value (protocol/v1 →
protocol/v2) marks a breaking boundary, and the affected rows carry a numbered
footnote explaining the break and which SDK range is required. Long provenance and
caveat text lives in the Notes footnote list below the table, so the table
itself stays compact — each cell is just a version, a range, or —. Each
Core release cell carries a small superscript footnote (e.g.
v0.0.1-alpha.5²) hanging off the release identifier; clicking it jumps to that
row’s provenance note at the bottom of the page.
Latest published versions
All four badges read the live latest published version, so they stay current
without maintenance. Python reads PyPI and Node reads npm’s rc
dist-tag. Core uses shields.io’s github/v/release endpoint
(include_prereleases&sort=semver): the monorepo carries a non-release spec/*
tag that pollutes plain github/v/tag semver sorting, and core cuts GitHub
Releases, so the release endpoint is the authoritative dynamic source. Go uses
github/v/tag (sort=semver) because go-sdk publishes version tags but no GitHub
Releases. Each badge links to the relevant registry or release/tag list for the
authoritative current version.
Core ↔ SDK matrix
| Core release | Status | Protocol | Python SDK | Node SDK | Go SDK |
|---|---|---|---|---|---|
| v0.0.1-rc.61 | current | protocol/v1 | 0.0.1-rc.6 (PyPI 0.0.1rc6) | npm @rc 0.0.1-rc.6 | v0.0.1-rc.6 |
| v0.0.1-rc.52 | supported | protocol/v1 | 0.0.1-rc.5 (PyPI 0.0.1rc5) | npm @rc 0.0.1-rc.5 | v0.0.1-rc.5 |
| v0.0.1-rc.43 | supported | protocol/v1 | 0.0.1-rc.4 (PyPI 0.0.1rc4) | npm @rc 0.0.1-rc.4 | v0.0.1-rc.4 |
| v0.0.1-rc.34 | supported | protocol/v1 | 0.0.1-rc.3 (PyPI 0.0.1rc3) | npm @rc 0.0.1-rc.3 | v0.0.1-rc.3 |
| v0.0.1-rc.25 | supported | protocol/v1 | 0.0.1-rc.2 (PyPI 0.0.1rc2) | npm @rc 0.0.1-rc.2 | v0.0.1-rc.2 |
| v0.0.1-rc.16 | supported | protocol/v1 | 0.0.1-rc.1 (PyPI 0.0.1rc1) | npm @rc 0.0.1-rc.1 | v0.0.1-rc.1 |
| v0.0.1-beta.47 | supported | protocol/v1 | 0.0.1-beta.4 (PyPI 0.0.1b4) | npm @beta 0.0.1-beta.4 | v0.0.1-beta.4 |
| v0.0.1-beta.38 | supported | protocol/v1 | 0.0.1-beta.3 (PyPI 0.0.1b3) | npm @beta 0.0.1-beta.3 | v0.0.1-beta.3 |
| v0.0.1-beta.29 | supported | protocol/v1 | 0.0.1-beta.2 (PyPI 0.0.1b2) | npm @beta 0.0.1-beta.2 | v0.0.1-beta.2 |
| v0.0.1-beta.110 | supported | protocol/v1 | 0.0.1-beta.1 (PyPI 0.0.1b1) | npm @beta 0.0.1-beta.1 | v0.0.1-beta.1 |
| v0.0.1-alpha.911 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.811 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.711 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.611 | supported | protocol/v1 | — | — | — |
| tested @ 9cf8a033 (post-v0.0.1-alpha.5, unreleased)12 | supported | protocol/v1 | PyPI 0.0.1a5 / git v0.0.2 | npm @alpha 0.0.1-alpha.5 | v0.0.1-alpha.4 |
| v0.0.1-alpha.513 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.414 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.315 | supported | protocol/v1 | — | — | — |
| v0.0.1-alpha.215 | previous | protocol/v1 | — | — | — |
| v0.0.1-alpha.115 | previous | protocol/v1 | — | — | — |
A cell of — means an exact, authoritative core↔SDK pairing could not be
determined from a published tag or a committed pin, so none is asserted. The
superscript on each Core release cell links that row to its provenance
footnote in the Notes list below.
Notes
Runtime requirements
| SDK | Runtime requirement | Install | Source |
|---|---|---|---|
| Python SDK | Python >=3.12,<4.0 | Install guide | python-sdk pyproject.toml [project].requires-python |
| Node SDK | Node.js >=18.18.0 (pnpm >=10 to build from source) | Install guide | node-sdk package.json [engines] |
| Go SDK | Go >=1.26.0 | Install guide | go-sdk go.mod (go directive) |
How this is maintained
This page is manifest-driven. The source of truth is
compatibility.toml
at the repository root. The matrix and requirements tables above are rendered from
it by docs/scripts/generate_compatibility.py; the content between the
BEGIN GENERATED / END GENERATED markers is generated — do not hand-edit it.
Edit the manifest and regenerate:
python3 docs/scripts/generate_compatibility.py
A CI step runs the same script with --check, so any drift between the manifest
and this page fails the build. The manifest is updated at each coordinated
release; every cell traces to a published tag, a registry release, or a committed
git pin (recorded in the Notes footnotes and the manifest comments). SDK cells
may be a single version or a range, and breaking changes are recorded by
bumping a row’s protocol value and adding a footnote — see the manifest comments
for the range and breaking-change conventions, including a commented example.
Last updated: 2026-07-17 by AI Agent Assembly Team
-
Latest published core tag and the current product line. Sixth release-candidate in the v0.0.1 series, cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk: all four repos carry a v0.0.1-rc.6 tag, PyPI publishes 0.0.1rc6, and npm publishes @agent-assembly/[email protected] under the rc dist-tag. Each SDK at this tag is built against the matching rc.6 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. Fifth release-candidate in the v0.0.1 series, cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk: all four repos carry a v0.0.1-rc.5 tag, PyPI publishes 0.0.1rc5, and npm publishes @agent-assembly/[email protected] under the rc dist-tag. Each SDK at this tag is built against the matching rc.5 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. Fourth release-candidate in the v0.0.1 series, cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk: all four repos carry a v0.0.1-rc.4 tag, PyPI publishes 0.0.1rc4, and npm publishes @agent-assembly/[email protected] under the rc dist-tag. A release-pipeline completeness cut with no wire-protocol change: it ships the previously-omitted aa-api-server binary and publishes the aa-gateway container image, adds a release-artifact completeness gate, builds every supported Python interpreter (cp312/cp313/cp314), bundles the Node native .node binding, and serves local-mode gRPC agent registration on loopback 127.0.0.1:50051. Each SDK at this tag is built against the matching rc.4 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. Third release-candidate in the v0.0.1 series, cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk: all four repos carry a v0.0.1-rc.3 tag, PyPI publishes 0.0.1rc3, and npm publishes @agent-assembly/[email protected] under the rc dist-tag. Each SDK at this tag is built against the matching rc.3 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. Second release-candidate in the v0.0.1 series, cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk: all four repos carry a v0.0.1-rc.2 tag, PyPI publishes 0.0.1rc2, and npm publishes @agent-assembly/[email protected] under the rc dist-tag. Each SDK at this tag is built against the matching rc.2 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. First release-candidate in the v0.0.1 series, promoting the channel up from beta. Coordinated across all four repos: agent-assembly + python-sdk + node-sdk + go-sdk each carry a v0.0.1-rc.1 tag (PyPI 0.0.1rc1, npm @agent-assembly/[email protected] under the rc dist-tag). ↩
-
Published core tag. Cut as a coordinated release across all four repos: agent-assembly + python-sdk + node-sdk + go-sdk each carry a v0.0.1-beta.4 tag (PyPI 0.0.1b4, npm @agent-assembly/[email protected] under the beta dist-tag). ↩
-
Published core tag. Cut as a coordinated release across all four repos: agent-assembly + python-sdk + node-sdk + go-sdk each carry a v0.0.1-beta.3 tag (PyPI 0.0.1b3, npm @agent-assembly/[email protected] under the beta dist-tag). ↩
-
Published core tag. Cut as a coordinated release across agent-assembly + python-sdk + node-sdk + go-sdk (monorepo AAASM-3004): all four repos carry a v0.0.1-beta.2 tag, PyPI publishes 0.0.1b2, and npm publishes @agent-assembly/[email protected] under the beta dist-tag. Each SDK at this tag is built against the matching beta.2 core revision, so the tag<->tag pairing is authoritative. ↩
-
Published core tag. First beta-channel pre-release in the v0.0.1 series (monorepo AAASM-2951), promoting the channel up from alpha. Coordinated across all four repos: agent-assembly + python-sdk + node-sdk + go-sdk each carry a v0.0.1-beta.1 tag (PyPI 0.0.1b1, npm @agent-assembly/[email protected]). ↩
-
Published core tag (2026-06-12 to 2026-06-14), between v0.0.1-alpha.5 and the beta.1 coordinated release. No SDK repository published a matching tag at this point in the alpha line: python-sdk’s alpha tags stop at v0.0.1-alpha.3, node-sdk’s at v0.0.1-alpha.4, and go-sdk’s at v0.0.1-alpha.5. SDK cells left as — per the accuracy contract. ↩ ↩2 ↩3 ↩4
-
All three SDKs pin aa-core/aa-proto/aa-sdk-client at git SHA 9cf8a033 (PR #958, 2026-06-05; 587 commits ahead of v0.0.1-alpha.5). This was the authoritatively-verified core<->SDK pairing before the beta line was cut. It is not a published core tag. ↩
-
Latest published core tag. No SDK tag pins exactly this commit; current SDKs pin a later SHA (see the pinned-commit row). SDK cells left as — to avoid asserting an unverified tag<->tag pairing. ↩
-
Published core tag. No SDK tag authoritatively pins this exact commit. ↩
-
Published core tag. SDK tags carrying the same version string (python/node/go of the matching alpha) exist, but at their tag time the SDK FFI crates did not yet pin a resolvable aa-core rev, so an exact commit<->commit pairing is NOT verifiable. Left as — per the accuracy contract. ↩ ↩2 ↩3
Source of truth & status
This hub routes across many independently shipped programs and repositories. Not all of them are public, and not all of them are generally available yet. This page is the canonical status map: for every documented area it records which repository owns the content, whether that source is public or private/internal, and whether the area is shipping today, in release candidate, or still planned.
When a page elsewhere in this hub describes a capability, look here first to know how much weight to put on it.
Status labels
Every area below is tagged with one visibility label and one maturity label.
Visibility — where the source lives and who can read it:
| Label | Meaning |
|---|---|
| 🟢 Public | Source repository is public on github.com/ai-agent-assembly; anyone can read it. |
| 🔒 Private / internal | Source repository is private; only the AI Agent Assembly team can read it. Documentation here describes intent, not a browsable codebase. |
Maturity — how much to trust the described behaviour:
| Label | Meaning |
|---|---|
| 🧪 Release candidate | Ships today as a release candidate; the API and behaviour are stabilizing but may still change before GA. The whole product is currently v0.0.1-rc. |
| 🗺️ Planned | Designed and documented as intent, but not yet generally available. Treat as a roadmap, not a contract. |
This is a documentation-area maturity axis, not the only one. The Horonomy
company site separately labels each product’s portfolio stage (available,
beta, release_candidate, coming_soon), which ranges over a product in the
company’s portfolio, not an area of this hub’s documentation. The two are
deliberately distinct axes with distinct owners — this page owns the one above —
and neither may be read as the other. See
ADR 0034 hand-off 7
for the full account.
Area status map
| Area | Owning repository | Visibility | Maturity | Where to read |
|---|---|---|---|---|
| Core (gateway, policy engine, eBPF, proxy, FFI, WASM, CLI, API) | agent-assembly | 🟢 Public | 🧪 Release candidate | core docs |
| Python SDK | python-sdk | 🟢 Public | 🧪 Release candidate | python-sdk docs |
| Node / TypeScript SDK | node-sdk | 🟢 Public | 🧪 Release candidate | node-sdk docs |
| Go SDK | go-sdk | 🟢 Public | 🧪 Release candidate | go-sdk docs |
| Arena (cross-framework governance trials) | arena | 🟢 Public | 🧪 Release candidate | arena docs |
| Runnable examples | examples | 🟢 Public | 🧪 Release candidate | repo README |
| Homebrew / install channel | homebrew-tap | 🟢 Public | 🧪 Release candidate | repo README |
| Specs (protocol & policy spec) | agent-assembly monorepo | 🟢 Public | 🧪 Release candidate | Policy reference · core docs |
| Releases (versions & compatibility) | this hub + each component’s tags | 🟢 Public | 🧪 Release candidate | Compatibility matrix |
| Cloud (SaaS control plane) | cloud | 🔒 Private / internal | 🗺️ Planned | Managed control plane — design preview |
| Enterprise (SSO, SCIM, advanced audit) | agent-assembly-enterprise | 🔒 Private / internal | 🗺️ Planned | Open core boundary |
| Operations (running & onboarding) | this hub | 🟢 Public | 🗺️ Planned | Managed SaaS onboarding — design preview |
The protocol specification stays in the
agent-assemblymonorepo by project policy. The reservedagent-assembly-specrepository is intentionally not used as the spec source.
Why some areas are private or planned
AI Agent Assembly is open core — limited-function self-host (Docker Compose), full-function SaaS. The enforcement path — every interception mechanism, the policy engine, the SDK shims, and the CLI — is open source and public. The commercial control plane (Cloud) and the enterprise operations features (Enterprise) are delivered as a managed SaaS and live in private repositories; their documentation here describes intended behaviour, not a browsable codebase. See the Open core boundary for the full split.
Last reviewed: 2026-07-11 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Page standards — progressive disclosure & mandatory metadata
This page is for anyone authoring or reviewing a page on this hub. It defines how deep a page goes, how it hands a reader deeper, and what metadata every page must carry so that a claim can be checked mechanically instead of by reading.
It exists because the same capability gets described at five different depths by five different authors, and the shallow descriptions are the ones that drift into being wrong. A reader who stops at the first paragraph should hold a correct picture, not a simplified one — those are different properties, and only the first is achievable by rule.
What governs this page
This page is downstream of four artifacts. It adds no claim to any of them, and it does not restate their definitions.
| Source | What it supplies | Where |
|---|---|---|
| ADR 0033 §6 — claim vocabulary | The eleven enforcement/claim terms. Four of the badge names below are §6’s words, reused verbatim. | ADR 0033 |
| ADR 0034 — one product truth | The three-axis ruling that decides which vocabulary may describe which subject, and the forbidden designs this page is checked against. | ADR 0034 |
| Content-layer ownership | The L0–L6 layer model and the canonical-owner table. | content-ownership.md |
| Product promise & message hierarchy | The worked instance of the four levels, for one page. | Product promise |
ADR 0034 is the governing document for the vocabulary question, and it is merged. It shipped as AAASM-5621’s deliverable, and this page is written against its hand-off 7 and forbidden-design list. This page was deliberately sequenced to land after it, so that its central ruling would never be published without a source; that ordering is now satisfied.
It is linked above in the
blob/HEADsource form rather than to the rendered docs site, because the published page —docs.agent-assembly.com/core/latest/adr/0034-…— still returns 404 while the site republishes after the merge, verified with ADR 0033’s published URL returning 200 as a control in the same check. When the rendered page resolves, swap that cell to the published URL, which is the formcontent-ownership.mdprefers for one rendered site linking another.
Ticket references are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
What this page does not decide
- Precedence between the three axes when they appear to conflict, and waivers. ADR 0034 owns both. This page applies its ruling; it does not extend it.
- Cross-repository adoption records and conflict resolution. Also ADR 0034 (AAASM-5621).
- Documentation-area maturity.
source-of-truth.mdowns it, and this page reads it rather than restating it — seeareaids. - A capability identifier registry. AAASM-5531 has landed
(
governance/capability-manifest.yamlinagent-assembly), and AAASM-5600 now validatescapability_idsagainst it wherever a page declares the field — see the field reference. It stays optional in schema version 1 rather than becoming required: rolling every hub page onto a populatedcapability_idslist is AAASM-5610’s adoption work, not this ticket’s, so requiring the field here would fail every existing page for a rollout this page does not own.
The four disclosure levels
The levels are a property of a reader’s need, not of a page’s length. Each level adds precision; none of them retracts what the level above said. A reader must be able to stop at any level and still be correct.
| Level | Name | Answers | Bound | Typical layer |
|---|---|---|---|---|
| 1 | One-sentence outcome | What do I get? | Exactly one sentence. Must carry the boundary clause — everything shorter drops it. | L0, L1 |
| 2 | Three-step product flow | How does it work, roughly? | Exactly three steps, each one short paragraph. | L1, L2 |
| 3 | Evaluator detail | What is on by default, and what does it not cover? | No length bound. Must state defaults and non-coverage. | L2, L3 |
| 4 | Implementation deep dive | How is it actually built, and on what evidence? | No bound of any kind. | L3, L6 |
Product promise is the reference instance for the levels: it carries all four for one subject. Read it as the worked example; this page is the general contract.
It is not yet a reference instance for the metadata.
product-promise.mdcarries no metadata block — it merged before this contract existed — so running a validator over it today produces a hard error for the missing block, and rule 13 additionally fires onprotects,enforcesandcatches, which appear there in double quotes as examples of banned wording. Both results are correct behaviour, not validator bugs. Adding blocks to existing hub pages is AAASM-5610’s work; the only page carrying one today is this one. The rule 13 double-quote exemption was added precisely because that page quotes the verbs it warns against.
Level 4 is never abbreviated
Depth is not a defect. No rule on this page — and no validator built from it — may be cited to remove technical detail from a component’s documentation, an ADR, a threat model or a protocol reference. There is no maximum page length, no maximum section count, and no requirement that a deep page carry a shallow summary of itself.
Progressive disclosure is about adding shallow entry points, never about
subtracting depth. A page that was thinned to “fit a level” has been damaged, not
improved. If a summary would replace its source, link the source instead — that is
content-ownership.md’s prohibition on a derivative reproducing its source at the
same depth.
Which levels a page must carry
Required levels are a function of page_type. A page may carry more levels than
required; it may never carry fewer. The one exception is level 4 on a product or
guide page, which is forbidden rather than optional — those types reach level 4
by handing off, because a page that both summarises and exhausts a subject is the
“derivative that reproduces its source at the same depth” content-ownership.md
prohibits. Rule 15 encodes this.
page_type | Must carry | May also carry | Reaches level 4 by |
|---|---|---|---|
product | 1, 2, 3 | — | a deeper link (4 is forbidden here) |
guide | 1, 3 | 2 | a deeper link (4 is forbidden here) |
reference | 3, 4 | 1, 2 | itself |
architecture | 3, 4 | 1, 2 | itself |
adr | 4 | 3 | itself |
Handing a reader deeper
A level boundary is a handoff, and an unmarked handoff is how a reader ends up treating a summary as the whole truth. Three rules:
- Every page that does not itself reach level 4 must name where level 4 is, in
the
deepermetadata key and as a visible link in the prose. A page whose deepest level is 3 and which offers no route to 4 is a dead end, and the validator rejects it. - The handoff link is the canonical source, not another summary. Linking a
sibling summary creates a chain of derivatives with no source at its end — the
default drift failure. The link form is
content-ownership.md’s: repo-relative within a repository;blob/HEADacross repositories in this org; the publisheddocs.agent-assembly.comURL from one rendered site to another. - A handoff may narrow, never widen. The shallower text must be true of everything the deeper text describes. If the deeper page states a platform, a precondition or a default that the shallower one omits, the shallower one has widened the claim and must be corrected — not the deeper one.
Badges and the ADR 0033 §6 reconciliation
This is the part most likely to be got wrong, so it is stated explicitly rather than left implicit in a table.
A claim vocabulary already exists and this page does not own it. ADR 0033 §6 defines eleven terms — Observed, Detected, Evaluated, Denied before execution, Redacted, Approval required, Degraded, Unmeasured, Experimental, Planned, Unsupported. Four of the eight badge names below are §6 words. Publishing a second definition for any of them would create exactly the two-vocabularies defect this programme exists to eliminate.
So the eight badges are not one enum, and — the part that matters — no §6 term is redefined here, and none is carried on an axis it does not belong to. Each badge sits on the axis that owns its subject, and is carried only by the key for that axis.
Two of the keys below are defined by this page and still carry a §6 term:
platforms[].status carries unsupported. That is not a counter-example — §6 itself
names the platform matrix row as Unsupported’s evidence, so the term is on its own
subject there. What the page never does is give a §6 term a meaning of its own, or put
one on a key whose subject §6 does not range over.
| Badge | Subject it ranges over | Definition owned by | Carried by |
|---|---|---|---|
available-verified | This capability in a published artifact | This page | availability, platforms[].status |
available-with-limits | This capability in a published artifact | This page | availability, platforms[].status |
preview | This capability in a published artifact | This page | availability |
deprecated | This capability in a published artifact | This page | availability |
experimental | One action, on evidence | ADR 0033 §6, verbatim | claims[].term only |
planned | One action, on evidence | ADR 0033 §6, verbatim | claims[].term only |
unsupported | This capability on one channel + platform | ADR 0033 §6, verbatim | platforms[].status only |
unmeasured | One action, on evidence | ADR 0033 §6, verbatim | claims[].term only |
There is no maturity key on this page, and that absence is deliberate — see
the axis ruling below.
The four that are ADR 0033 §6 terms reused verbatim
experimental, planned, unsupported and unmeasured are §6’s terms. This
page does not define them, does not paraphrase them, and does not narrow them. It
specifies only where they are carried and how they are rendered. For their meaning
and their required evidence, ADR 0033 §6 is the source — go there.
Three scoping consequences follow from §6’s own text and are recorded here because a validator needs them:
experimentalandplannedare claim terms, so they live inclaims[]. They are not values of any key this page owns. A capability that is decided but not implemented is recorded asclaims: [{term: Planned, evidence: <ticket>}]— with noavailabilityvalue at all, because a planned capability is in no artifact.unsupportedis per (channel, platform), never page-level. §6 names “the platform matrix row” as its required evidence, so the term belongs in aplatforms[]row and is rejected everywhere else. Platform names follow ADR 0033 §5.3’s matrix rows —linux-x86_64,linux-aarch64,macos,windows— rather than a finer split §5.3 does not make.unmeasuredis per-action, never page-level. §6 scopes it to an action or payload, and explicitly notes that a connection-level observation may still exist for the same traffic. Using it to mean “we did not check whether this ships” would be a redefinition, so it is rejected everywhere exceptclaims[].term. A distribution fact that was never checked is not a badge at all — it is a missingplatforms[]row, which is a validation error.
The four that are this page’s, on their own subject
available-verified, available-with-limits, preview and deprecated do not
appear anywhere in ADR 0033 — verified as zero occurrences against the ADR text, with
Unmeasured, Unsupported, Experimental and Planned as positive controls in the
same probe. Every one of them answers a single question — what can a reader obtain
from a published artifact, and how much may they rely on it? — and none of them
says how finished anything is, or what it does to an action.
| Badge | Means | Required evidence |
|---|---|---|
available-verified | Present in every published artifact named by a platforms[] row, at the named version, checked against a published tag | A platforms[].evidence string per shipping row, plus last_verified |
available-with-limits | Present, but a stated limit changes what a reader may rely on | The above, plus a non-empty limitations |
preview | Present, but outside the compatibility commitment — it may change without a deprecation cycle | The above |
deprecated | Present, and scheduled for removal | The above, plus a limitations naming the replacement |
available-verifiedis an availability statement, not an enforcement claim. It asserts that the capability is present in a published artifact. It asserts nothing about what the capability does to an action — that requires a §6 term inclaims[]. Writingavailable-verifiedand expecting a reader to infer protection is the promotion errorcontent-ownership.mdlists among the moves that widen a claim, and ADR 0034 forbidden design 12 bans it by name.
Which axis owns which word — settled by ADR 0034
An earlier draft of this page recorded this question as open and deferred it to AAASM-5621. It is no longer open. ADR 0034 — the AAASM-5621 deliverable — settles it, and this page is built against that ruling rather than around it.
Hand-off 7 of ADR 0034 rules that there are three axes, each ranging over a different subject, and that no axis may be applied to another’s subject:
| Axis | Vocabulary | Owner | Ranges over |
|---|---|---|---|
| Behaviour on evidence | ADR 0033 §6’s eleven claim terms | ADR 0033 §6 (Core) | One action on one host, at one time |
| Documentation-area maturity | 🧪 Release candidate, 🗺️ Planned | Docs Hub source-of-truth.md | One area of Agent Assembly documentation |
| Portfolio lifecycle | available, beta, release_candidate, coming_soon | The company site’s pinned product registry | One product in the Horonomy portfolio |
Forbidden design 12 then bans “applying a maturity label as a behaviour claim, a claim term as a completeness claim, or a portfolio lifecycle value to either … and coining a term on the claim axis — one naming a behaviour-on-evidence outcome — that ADR 0033 §6 does not define.”
That second clause is scoped to the claim axis, and to it alone. §6 owns the first
axis only; 🧪 Release candidate and 🗺️ Planned are the Docs Hub’s terms and the
portfolio lifecycle values are the company registry’s, and §6 defines none of them.
A new term on a non-claim axis is governed by that axis’s owner, not by §6.
Three consequences, all of which this page obeys:
- No
maturitykey. A page does not restate its area’s maturity. Documentation- area maturity ranges over an area, not a page, andsource-of-truth.mdowns it — so it is read from the area row, via this page’sareakey, and never copied into a page’s metadata. Copying it would both duplicate a generated value and apply an area-scoped label to a page-scoped subject. - No §6 term as a completeness value.
experimentalandplannedare claim terms; carrying them under a key named for completeness is forbidden design 12’s second clause exactly. They are inclaims[]. availabilitycoins nothing on the claim axis, so its owner is this page. Its subject — a capability’s presence in a published artifact — is none of hand-off 7’s three: not an action, not a documentation area, not a portfolio product. It is the subject this ticket exists to make recordable, because distribution here is per channel and per platform and no existing vocabulary expresses it. Being a non-claim axis, it is governed by its own axis owner under forbidden design 12’s scoping, and the four values below are that owner’s to define. §6 supplies the negative value for the same subject (Unsupported, whose stated evidence is the platform matrix row) but has no positive counterpart, which is why the positives are defined here and the negative is reused verbatim.
Nothing on this page is a term on the claim axis. Every behaviour-on-evidence
statement a page makes is a §6 term in claims[], spelled exactly as §6 spells it.
Visual treatment
Badges render as inline spans, styled by the brand stylesheet. The text is the badge; colour is redundant reinforcement, never the only carrier of meaning — the hub’s accessibility baseline requires that.
<span class="aa-badge aa-badge--available-verified">Available (verified)</span>
<span class="aa-badge aa-badge--unsupported">Unsupported</span>
| Badge | Class suffix | Tone |
|---|---|---|
available-verified | --available-verified | positive |
available-with-limits | --available-with-limits | caution |
preview | --preview | caution |
experimental | --experimental | caution |
planned | --planned | neutral |
deprecated | --deprecated | caution |
unsupported | --unsupported | negative |
unmeasured | --unmeasured | neutral |
A badge whose term is owned by ADR 0033 §6 must link to §6 on first use on a page, so a reader can reach the definition rather than infer it.
The metadata block
Where it lives, and why not front-matter
mdBook does not support YAML front-matter — it would render as literal text at the top
of the page. The block is therefore an HTML comment, which mdBook passes through
without rendering, placed as the first construct in the file, before the # H1.
A sidecar manifest keyed by page path was considered — it would match the repo’s
existing hub-components.toml / compatibility.toml precedent — and rejected: page
metadata that lives away from its page drifts from it, and a new page acquires a row
only if someone remembers. In-page metadata is edited by the same person, in the same
commit, as the prose it describes.
The shape — this is an illustration of placement, not a template to copy; the
... stands for the remaining keys, and a real block never contains it. Copyable
blocks are in Page templates:
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: product
...
END AA-PAGE-META -->
Parsing contract for AAASM-5601:
- The delimiter match is anchored, not a substring search. A line opens a block
only if its content, after stripping leading and trailing whitespace, begins with
the literal
<!-- BEGIN AA-PAGE-META; a line closes it only if its stripped content equalsEND AA-PAGE-META -->. A mention of the literal in the middle of a sentence is not a delimiter. - Delimiters inside fenced code blocks or inline code spans do not open a block. Strip fenced regions and inline code spans before scanning — the same exemption rule 13 uses, and for the same reason.
- This page is the proof that both of the rules above are needed. The literal
<!-- BEGIN AA-PAGE-METAoccurs on nine lines here: one real block, six inside fenced templates, and two inline — in the bullet above and in this sentence. A scanner that stripped fences but not inline code, or that matched anywhere in a line rather than at its start, would find three BEGIN delimiters and reject the page that defines the format. - The body is the lines strictly between the two delimiter lines, parsed as a YAML 1.2 mapping.
- The body must not contain the two-character sequence
--, because that is not legal inside an HTML comment. The delimiter lines themselves are excluded from this check — they necessarily contain<!--and-->. Single hyphens in enum values are fine. A--in the body is a hard error. - Exactly one block per file. Zero blocks, two blocks, or a block that is not the first construct is a hard error.
- Unknown keys are a hard error, not a warning — a typo’d key is otherwise a silently absent required field.
Failure modes
Every rule below resolves to exactly one of these, so 5601 needs no judgement call:
| Outcome | Meaning | Effect |
|---|---|---|
| error | The page is invalid | Build/CI fails |
| warning | The page is valid but stale or degrading | Reported, does not fail |
Field reference
R = required, O = optional, C = conditional (see
cross-field rules).
| Key | Type | Req | Allowed values | Missing / invalid |
|---|---|---|---|---|
schema_version | integer | R | 1 | error |
page_type | string | R | product · guide · reference · architecture · adr | error |
audience | list of string, non-empty | R | evaluator · developer · operator · security-engineer · contributor · auditor | error |
user_job | string | R | 10–120 chars; one sentence — no interior period-then-space, no trailing period | error |
owner | string | R | L<n>:<surface>, exactly as paired in the surface table | error |
canonical_source | string | R | self, or a link in the canonical-link form | error |
describes_capability | boolean | R | true · false | error |
area | string | C | one of the 12 area ids | error if required and absent |
availability | string | C | available-verified · available-with-limits · preview · deprecated | error |
platforms | list of object | C | see platforms[] | error |
last_verified | object | C | see last_verified | error |
claims | list of object | C | see claims[] | error |
limitations | string | C | non-empty; a link or an in-page anchor | error |
disclosure_levels | list of integer, non-empty | R | subset of [1,2,3,4], ascending, no duplicates | error |
deeper | string | C | a link in the canonical-link form | error |
capability_ids | list of string | O | each entry must resolve to a capabilities[].id row in governance/capability-manifest.yaml (validated by docs/scripts/validate_capability_ids.py, AAASM-5600); still optional in schema version 1 — see what this page hands off | error |
There is deliberately no maturity key — see
the axis ruling. A page’s
documentation-area maturity is read from its area row, not restated
here.
owner surfaces
owner names the layer that owns the content, not the repo the file sits in — a
Docs Hub page summarising a Core fact is owned by Core.
The value must be one of these eleven pairs, exactly as written. The pairing is fixed here rather than by reference, so a validator needs no cross-repository lookup:
owner | Layer | Surface |
|---|---|---|
L0:horonomy.dev | L0 Company site | horonomy.dev |
L1:official-website | L1 Product website | official-website |
L2:docs | L2 Docs Hub | docs |
L3:agent-assembly | L3 Component docs | agent-assembly (Core) |
L3:python-sdk | L3 Component docs | python-sdk |
L3:node-sdk | L3 Component docs | node-sdk |
L3:go-sdk | L3 Component docs | go-sdk |
L3:arena | L3 Component docs | arena |
L3:cloud | L3 Component docs (private) | cloud |
L3:agent-assembly-enterprise | L3 Component docs (private) | agent-assembly-enterprise |
L4:examples | L4 Examples | examples |
Any other value — including a right-hand surface paired with the wrong layer — is an error.
Three notes on the boundaries of this table, because each one is a question a validator author would otherwise have to guess at:
cloudandagent-assembly-enterpriseare L3.content-ownership.md’s layer table does not list them, but its prose is explicit that a private repository “is an L3 component for its own contributors and is outside the public content boundary”. Their reader-facing pages are published as L2 Docs Hub pages, so a hub page about the managed service isL2:docs;L3:cloudnames the private component only, and what may be said about it is bounded by the SaaS claim publication checklist.- L5 and L6 cannot be owners. L5 is a repository README and L6 is code, generated
specs and evidence —
content-ownership.mdstates that nothing in L6 is a reader-facing page. Neither owns a page’s content, so no pair exists for them, andowneraccepts noL5:orL6:value. That a level-4 section cites L6 evidence is a different relationship from L6 owning the page. - The layer is the content’s, not the file’s. This page lives in the
docsrepo but a page here that summarises a Core fact carriesL3:agent-assemblyand acanonical_sourcelink, per rule 9. canonical_source: selfis available only toL2:docsand theL3:surfaces. Rule 9 turns on theownersurface naming the repository the page is in, and only those nine surfaces are repository names.L0:horonomy.devis a domain — its repository ishoronomy-official-website, in a different organisation — andL1:official-websiteandL4:examplesname repositories this contract is not applied in. Pages under those owners always carry a link.
area ids
area ties a page to one row of the status map in
source-of-truth.md. That row carries the page’s
documentation-area maturity, which is therefore read from the status map and
published beside the page’s other metadata rather than restated inside it.
No validation rule derives anything from the area’s maturity label — see
why there is no such rule.
area identifies the row; the reader gets both the area label and the page’s
claims[], each checked against its own owner.
The row cannot be identified by name, because the area names exist in three
incompatible forms: the rendered table cell (**Node / TypeScript SDK**), the
short_name in hub-components.toml (Node SDK), and — for five of the twelve —
neither, because Specs, Releases, Cloud, Enterprise and Operations are literal
strings inside generate_hub_components.py rather than manifest rows. So area
takes a stable id, and this table is the mapping:
area id | Row identified by this exact Area cell |
|---|---|
core | **Core** (gateway, policy engine, eBPF, proxy, FFI, WASM, CLI, API) |
python-sdk | **Python SDK** |
node-sdk | **Node / TypeScript SDK** |
go-sdk | **Go SDK** |
arena | **Arena** (cross-framework governance trials) |
examples | **Runnable examples** |
homebrew | **Homebrew / install channel** |
specs | **Specs** (protocol & policy spec) |
releases | **Releases** (versions & compatibility) |
cloud | **Cloud** (SaaS control plane) |
enterprise | **Enterprise** (SSO, SCIM, advanced audit) |
operations | **Operations** (running & onboarding) |
Resolution procedure, so no step is a judgement call: map the area id to its
Area cell using the table above; find the row in source-of-truth.md’s
BEGIN GENERATED:hub-components:source-of-truth-table region whose first cell matches
that string exactly; read its Maturity cell. A missing or ambiguous match is an
error — it means the status map changed and this table was not updated with it.
This mapping is hand-maintained, and that is a known weakness. It duplicates identifiers that a generator should emit. The durable fix is a stable
idper area inhub-components.tomland in the generator’s five literal rows, with this table generated from it — recorded as a hand-off to AAASM-5601, which owns the tooling. It is not done here becausehub-components.tomland the generator belong to the status-map pipeline, not to this page, and changing them is a separate concern from defining the metadata contract. Until then, an area rename requires editing this table in the same PR.
platforms[]
Distribution in this product is per channel and per platform: a capability can ship on one channel and not another. A single “released” boolean is therefore not expressible, and is not offered. Each row is one (channel, platform) pair.
Relationship to ADR 0034 §6.1’s
released_channels/released_platforms/released_matrix. Same shape, different surface, and deliberately not merged into one name. §6.1’s fields belong to the capability manifest AAASM-5531 will publish — one record per capability, across the whole product.platforms[]here is page metadata: what this page’s subject ships on, written by the page’s author. When 5531 lands,platforms[]becomes derivable fromreleased_matrixand this page should say so rather than keeping a second hand-maintained copy — that is the same hand-offcapability_idsalready carries. Recording the correspondence now is what stops a third spelling appearing later.Note also that
availabilityanswers only §6.1’s Distributed? question. Buildable? and Activated? are separate questions with separate fields (default_state,reachability), and no value on this page may be read as answering them — a capability can ship in an artifact and still be unreachable in it.
| Key | Type | Req | Allowed values |
|---|---|---|---|
channel | string | R | github-release · homebrew · ghcr · install-sh · crates-io |
platform | string | R | linux-x86_64 · linux-aarch64 · macos · windows |
status | string | R | available-verified · available-with-limits · unsupported |
evidence | string | C | non-empty; required when status is not unsupported |
- Duplicate (
channel,platform) pairs are an error. - A pair that is absent asserts nothing, and asserting nothing about a channel a
page’s capability plausibly ships on is the gap this field exists to close. A page
with
describes_capability: truemust therefore enumerate a row for every channel in the enum, usingunsupportedwhere it does not ship. Partial enumeration is an error — except where rule 4 applies, in which caseplatformsis exactly[]and no row is written at all. unsupportedrequires noevidencestring because ADR 0033 §5.3’s matrix row is its evidence, per §6.- Enumeration is per channel, and platform coverage within a channel is deliberately
partial. The rule closes the gap that matters most — a channel a capability
plausibly ships on being passed over in silence — and stops short of the full
(channel × platform) cross product, which is twenty rows for a fact that is usually
uniform across platforms within a channel. So a page naming
github-release×linux-x86_64asserts nothing aboutgithub-release×windows. Where the platform distinction is the point, write the extra rows: they are permitted, and only the per-channel minimum is enforced.
last_verified
| Key | Type | Req | Allowed values |
|---|---|---|---|
version | string | R | a release version, e.g. v0.0.1-rc.6 |
ref | string | R | a tag matching ^v\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?$, or a 40-character hex SHA |
date | string | R | ISO 8601 YYYY-MM-DD |
method | string | R | non-empty, ≤ 200 chars — how it was checked |
Evidence taken from a branch does not describe a published artifact. A reader asking “does this ship?” is asking about a tag. The literal values
main,masterandHEADare therefore hard errors inref, as is any value that is neither a tag nor a full SHA. If the only evidence available is from a branch, the honest record is aplatforms[]row you cannot yet fill — not arefthat overstates.
Freshness:
| Condition | Outcome |
|---|---|
date more than 180 days old | error — evidence is stale |
date more than 90 days old | warning |
version differs from the current release | warning |
date in the future | error |
“The current release” is the core value of the single [[release]] table in
compatibility.toml whose status = "current" — at time of writing v0.0.1-rc.6.
Naming the key matters: compatibility.toml holds one [[release]] per supported
version, so “the version in compatibility.toml” would otherwise match several.
Parse the TOML; do not grep it. A parser returns exactly one table with
status = "current". A grep for the string returns two — the second occurrence
is inside a commented-out worked example further down the file, and an implementer who
takes the last match, or errors on finding two, gets the wrong answer from a file that
is actually unambiguous.
claims[]
Zero or more. Each entry:
| Key | Type | Req | Allowed values |
|---|---|---|---|
term | string | R | one of ADR 0033 §6’s eleven terms, verbatim |
evidence | string | R | non-empty — a link, or an E-block reference into the public claim inventory |
subject | string | O | self (the default — this page’s own subject), or an owner value naming a different component the claim is actually about |
The permitted term values are §6’s whole set, not a subset:
Observed · Detected · Evaluated · Denied before execution · Redacted ·
Approval required · Degraded · Unmeasured · Experimental · Planned ·
Unsupported. Restricting the list here would be a redefinition of someone else’s
vocabulary; extending it would be worse. If §6 gains or loses a term, this enum
follows it — §6 is the source, and a mismatch is a bug in this page.
claims[] is a complete index of every claim the page states, including one
about a different component — that completeness is the reason subject exists rather
than leaving a foreign claim to prose alone. See
rule 4’s foreign-subject carve-out for why a
subject other than self changes what rule 4 requires elsewhere on the page.
Cross-field rules
These are the rules a prose field list cannot express, and they are where most of the validation value is.
| # | Rule | Outcome if violated |
|---|---|---|
| 1 | describes_capability: true ⇒ area, platforms, last_verified and claims all present | error |
| 2 | describes_capability: false ⇒ area, availability, platforms, last_verified, claims, limitations all absent | error |
| 3 | availability: available-with-limits or deprecated ⇒ limitations present and non-empty | error |
| 4 | claims[] contains a Planned entry with subject: self (or no subject) ⇒ availability absent, platforms exactly [], and claims contains no other self-subject entry | error |
| 5 | availability: available-verified ⇒ every platforms[] row has status ∈ {available-verified, unsupported} — no row may be available-with-limits | error |
| 6 | availability is present iff describes_capability: true and claims[] does not contain a self-subject Planned | error |
| 7 | platforms[].status may never be preview or deprecated, and never a §6 term other than unsupported | error |
| 8 | claims[].term may never be a value outside §6’s eleven | error |
| 9 | canonical_source: self ⇒ the owner surface names the repository the page is in; otherwise canonical_source must be a link | error |
| 10 | canonical_source other than self must match the canonical-link form: repo-relative, https://github.com/<org>/<repo>/blob/HEAD/<path>, or https://docs.agent-assembly.com/<path>. A branch-name blob URL is rejected | error |
| 11 | max(disclosure_levels) < 4 ⇒ deeper present | error |
| 12 | disclosure_levels must be a subset of the page type’s must ∪ may levels, and include all of its must levels — see the table | error |
| 13 | An unbounded claim verb appears in the body ⇒ describes_capability: true, claims non-empty, and limitations present | error |
| 14 | claims[] contains any of Observed, Detected, Evaluated, Denied before execution, Redacted, Approval required, Degraded ⇒ limitations present and non-empty | error |
| 15 | page_type is product or guide ⇒ 4 ∉ disclosure_levels (those types reach level 4 by a deeper link, never in the page) | error |
There is no rule coupling the area label to a claim term
An earlier draft carried a rule 13 requiring a 🗺️ Planned area’s pages to claim
Planned, and forbidding the claim on 🧪 Release candidate areas. It is
withdrawn, and no rule replaces it.
It was a forbidden design. The area label is on the documentation-area axis and
claims[].term is on the behaviour axis, and ADR 0034 hand-off 7 states that no axis
may be applied to another’s subject — a documentation-area label says nothing about
an action’s behaviour. Removing maturity took that collapse out of the key but
left it in the rule, which is the subtler half of the same defect.
It also produced wrong answers on ordinary pages, in both directions:
- An
operationspage documenting a capability that genuinely ships would have been forced to claimPlanned, which rule 4 then forces toplatforms: []— asserting the capability is in no published artifact, which is false, and which this page elsewhere calls a validation error. - A
corepage documenting a genuinely planned capability — the Windows host adapter,Unsupportedin ADR 0033 §5.3 — could not have used §6’sPlannedat all, though that is the term §6 defines for exactly this case.
The correct treatment is a publication rule, not a metadata one. ADR 0034 hand-off
1 prescribes it: split the statement into a behaviour claim and a completeness claim,
check each against its own owner, publish both, and let the more restrictive published
outcome govern the surface. So a page carries its area label and its claims[] side
by side, each validated against its own owner, and neither constrains the other. The
internal consistency the withdrawn rule was reaching for is already carried by rules 4
and 6, which govern claims[] and availability — both on axes this page may bind
together, because availability is not §6’s.
Rule numbering was closed up rather than leaving a gap; the rules formerly numbered 14, 15 and 16 are now 13, 14 and 15.
Rule 4 and the enumeration carve-out
Rule 4 is the only place platforms may be empty, and it is the reason the
full-enumeration requirement carries an explicit exception. A capability
that is Planned is in no artifact, so there is no channel row to write and no
availability to state — enumerating five unsupported rows for it would assert a
platform result where §6 requires a ticket reference and no capability claim.
The three 🗺️ Planned areas today are cloud, enterprise and operations.
quickstart-saas.md and cloud-deployment.md already take this path, landed
under AAASM-5613 rather than AAASM-5610’s later sweep. Rules 4 and 6 are
written to agree with each other on exactly that case.
A foreign-subject Planned claim does not take this path. AAASM-5762: a page
whose own subject genuinely ships — real platforms[] rows, a real availability
value — may still need to honestly record a Planned gap in a different
component (found first while drafting the evaluator guides: a page had to record the
SDK-side audit gap, ADR 0033 §6 Planned, without that being a completeness
statement about the page’s own subject). Before subject existed, an author in this
position had exactly three bad options: put the claim in claims[] and publish false
platforms/availability for a subject that is not planned at all; state it in prose
only, so claims[] silently stops being a complete index; or drop the claim.
subject removes the fork. A claims[] entry whose subject names a different
owner is a claim about that component, not this page’s — rule 4 does not fire on
it, rule 6 does not treat it as a reason to drop availability, and it may sit
alongside the page’s own real, present-tense claims. It still satisfies rule 8 (a §6
term) and, if its term is one of rule 14’s list, rule 14’s limitations requirement —
those are about the claim, not the subject, and apply unchanged. Only rules 4 and
6, which are specifically about what a self-subject Planned implies for this
page’s own platforms/availability, are scoped by it.
The control, worked by hand ahead of AAASM-5601 implementing it: this passes —
describes_capability: true
area: core
availability: available-with-limits
limitations: "#limits"
platforms:
- {channel: github-release, platform: linux-x86_64, status: available-verified, evidence: "..."}
claims:
- {term: Evaluated, evidence: "..."}
- {term: Planned, subject: L3:python-sdk, evidence: "AAASM-5750"}
Rule 4 does not see a self-subject Planned (the second entry’s subject is
L3:python-sdk, not self), so it does not require availability absent or
platforms: [] — the page’s own, real available-with-limits and non-empty
platforms[] stand. Rule 6 agrees for the same reason. This still fails —
describes_capability: true
area: core
availability: available-with-limits
limitations: "#limits"
platforms:
- {channel: github-release, platform: linux-x86_64, status: available-verified, evidence: "..."}
claims:
- {term: Planned, evidence: "..."}
— because this Planned entry has no subject, which defaults to self: rule 4
fires, and availability present plus platforms non-empty both violate it.
Rule 14 — why the verb list is not enough
Rule 13 keys off English prose; rule 14 keys off a declared enum, and the second is
strictly the more reliable of the two. Without rule 14 a page can declare the
strongest term in §6’s vocabulary — Denied before execution — with availability: available-verified and an empty limitations, and pass every other rule: rule 3 does
not fire because the availability value is not available-with-limits, and rule 13
does not fire if the prose avoids the five listed verbs. Publishing the product’s
strongest enforcement claim with no stated limitation is precisely what this page’s
second acceptance criterion forbids, so the rule closes it from the metadata side.
The seven terms it covers are every §6 term that asserts a control did something to an action or its payload. Only four are excluded, and each because it asserts the opposite — that no control acted, or that none exists yet:
| Excluded term | Why |
|---|---|
Unmeasured | §6: no control inspected the action; there is no capability to bound |
Unsupported | §6: not available on this platform/configuration |
Planned | Decided but not implemented, and §6 attaches no capability claim; rules 4 and 6 already force platforms: [] and forbid availability |
Experimental | Implemented but not validated for production — §6 requires the missing validation be named, which is itself the bound |
ObservedandDetectedare in the rule, not excluded from it. An earlier draft excluded them as reporting “an absence of control”, which is simply wrong: §6 definesObservedas an event reached the evidence pipeline andDetectedas a pattern of interest was found — both are positive capability claims, and neither is bounded anywhere else in this page. Excluding them left the most historically dangerous claim in this product unbounded: ADR 0033 cites “eBPF sensor catches kernel-level bypass attempts” as a forbidden design, and §6 maps the eBPF syscall guard toDetectedwith an explicit not Denied before execution caveat. A page claimingDetectedwith no stated limitation is exactly that defect, so rule 14 now covers it.
Rule 13 — the unbounded claim verbs
Rule 13 is the mechanical form of “public pages cannot omit status and limitations when the claim depends on them”. Product promise already instructs authors to pick a §6 term for every verb; rule 13 restates that requirement from the metadata side, so a page cannot satisfy it by wording alone.
The closed list, matched case-insensitively on word boundaries, as these literal forms only — no inflection expansion:
protects · enforces · catches · prevents · guarantees
The first three are the three verbs ADR 0033 §6 names by name when it requires that
downstream material pick one of its terms rather than an undifferentiated verb like
protects, enforces or catches. Taking §6’s own examples is the least inventive
possible choice of list.
Exempt occurrences, which a validator must strip before matching:
- Fenced code blocks.
- Inline code spans.
- Text inside straight double quotes (
"…") or typographic double quotes ("…").
Exemption 3 exists because a page discussing the rule quotes the banned verbs in prose. It is mechanical — quote characters, not intent — and it is the difference between this rule being usable and being wrong on the very pages that explain it.
Two properties of exemption 3 that a prose statement would leave to the implementer, and which decide whether two conforming validators agree:
- Quoted spans are matched across the whole document, not per line. A quotation that wraps onto a second line is one span. This is the one place the page is not line-oriented, and it is called out because every other parsing rule here is — delimiters are matched per line, and an implementer who carried that habit into exemption 3 would get a different answer on a wrapped quotation.
- An odd number of straight double quotes in a document is an error, not a silently-shifted pairing. Quotes are paired left to right; an unmatched final quote means every subsequent pairing is offset, so the honest outcome is to reject the page rather than emit a result that depends on where the imbalance happened to fall. Typographic quotes pair by direction and are exempt from the count.
Prefer inline code over quotation when naming a banned verb. This page names all five in backticks where it lists them; one further occurrence sits inside a quoted ADR citation and does rely on exemption 3, which is the legitimate use — a page genuinely quoting a source. Measured: deleting exemption 3 leaves exactly one hit on this page, that citation. The earlier draft quoted three of the verbs in bare prose and was, correctly, the first page to expose the ambiguity above; exemption 3 should not be the mechanism a page relies on to discuss the rule, only to quote a source.
The list is deliberately high-precision, and it is a floor rather than a ceiling. The obvious longer list — adding
blocks,stops,secures,ensuresand the bare infinitives — was tested against this page and rejected:blocksalone matches “code blocks”, “fenced blocks” and “E-blocks” several times here, none of them a product claim. A gate that fires on a common noun gets switched off, and a gate that is off finds nothing. Third-person singular is the form an actual capability claim takes (Agent Assembly protects …), so that is what is matched.False negatives are therefore expected and accepted. Rule 13 does not replace the editorial rule in Product promise — if the sentence works with an undifferentiated verb, it is not specific enough to publish — it only makes the most common case unmissable. Rule 14, which keys off a declared enum rather than English, is the stronger of the two.
A page that uses one of these verbs in prose and declares describes_capability: false has mis-declared its type, and that is an error rather than a warning: it is
the exact combination that lets an unevidenced claim through unchecked.
Page templates
Five templates, one per page_type. They are the required skeleton; a page may add
sections freely. Copy the metadata block and the headings, then write.
Every template below carries a complete, parseable block — opening delimiter, YAML
body, END AA-PAGE-META --> terminator. None uses an elided ... form, because a
template an author copies verbatim has to validate verbatim.
Templates are versioned by template_version below, which moves with
schema_version. A template change that adds a required section or changes a key is a
major change and needs a new schema_version plus a migration row in
the changelog.
Current template_version: 1 (matches schema_version: 1).
product — describes what the product does for a reader
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: product
audience: [evaluator]
user_job: Decide whether this capability meets my requirement
owner: L3:agent-assembly
canonical_source: https://docs.agent-assembly.com/core/latest/...
describes_capability: true
area: core
availability: available-with-limits
limitations: "#limits"
platforms:
- {channel: github-release, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: homebrew, platform: macos, status: available-verified, evidence: "..."}
- {channel: ghcr, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: install-sh, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: crates-io, platform: linux-x86_64, status: available-verified, evidence: "..."}
last_verified: {version: v0.0.1-rc.6, ref: v0.0.1-rc.6, date: 2026-08-06, method: "..."}
claims:
- {term: Evaluated, evidence: "..."}
disclosure_levels: [1, 2, 3]
deeper: https://docs.agent-assembly.com/core/latest/...
END AA-PAGE-META -->
# <Capability>
<Level 1 — one sentence, including the boundary clause.>
## How it works
<Level 2 — exactly three steps.>
## For an evaluator
<Level 3 — defaults, and what is not covered.>
## Limits
<Every limit the maturity badge depends on.>
## Going deeper
<The level-4 handoff link.>
guide — a task a reader performs
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: guide
audience: [operator]
user_job: Route an agent through the proxy on a single host
owner: L3:agent-assembly
canonical_source: https://docs.agent-assembly.com/core/latest/...
describes_capability: false
disclosure_levels: [1, 3]
deeper: https://docs.agent-assembly.com/core/latest/...
END AA-PAGE-META -->
# <Task>
<Level 1 — what you will have when you finish.>
## Before you start
<Preconditions. Every one of them — a dropped precondition is a widened claim.>
## Steps
<The task.>
## What this does not do
<Level 3 — the boundary of the outcome.>
## Going deeper
<The level-4 handoff link.>
reference — the authoritative surface for something
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: reference
audience: [developer, operator]
user_job: Look up the exact behaviour of one policy field
owner: L3:agent-assembly
canonical_source: https://docs.agent-assembly.com/core/latest/...
describes_capability: false
disclosure_levels: [3, 4]
END AA-PAGE-META -->
# <Subject> reference
<Who this is for and what it covers.>
## Scope
<What is in this reference and what is deliberately not.>
## <Reference body>
<Level 3 and level 4. No length bound.>
architecture — how something is built and why
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: architecture
audience: [security-engineer, contributor]
user_job: Understand how the proxy decides before it dials upstream
owner: L3:agent-assembly
canonical_source: https://docs.agent-assembly.com/core/latest/...
describes_capability: true
area: core
availability: available-with-limits
limitations: "#boundaries-and-non-goals"
platforms:
- {channel: github-release, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: homebrew, platform: macos, status: available-verified, evidence: "..."}
- {channel: ghcr, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: install-sh, platform: linux-x86_64, status: available-verified, evidence: "..."}
- {channel: crates-io, platform: linux-x86_64, status: available-verified, evidence: "..."}
last_verified: {version: v0.0.1-rc.6, ref: v0.0.1-rc.6, date: 2026-08-06, method: "..."}
claims:
- {term: Denied before execution, evidence: "..."}
disclosure_levels: [3, 4]
END AA-PAGE-META -->
# <Component or subsystem>
## Context
<The problem, and the constraints that shape the design.>
## Design
<Level 4. Full depth. Diagrams, data flow, failure behaviour.>
## Boundaries and non-goals
<What it deliberately does not do.>
## Evidence
<Where each claim on this page is checked.>
adr — a recorded decision
An ADR keeps the format of the ADR set it belongs to; this template adds the metadata
block and nothing else. ADRs live in the component repository that owns the decision —
per content-ownership.md, this hub does not author them.
Note the canonical_source: self paired with owner: L3:agent-assembly: an ADR is
the canonical source for its decision. That combination is valid under
rule 9 only when the validator runs in the agent-assembly
repository, which is where the page lives — the same block placed on a Docs Hub page
would be rejected, correctly, as a hub page cannot be canonical for a Core decision.
<!-- BEGIN AA-PAGE-META
schema_version: 1
page_type: adr
audience: [contributor, auditor]
user_job: Understand why this decision was taken and what it binds
owner: L3:agent-assembly
canonical_source: self
describes_capability: false
disclosure_levels: [4]
END AA-PAGE-META -->
# ADR NNNN: <Title>
## Status
## Context
## Decision
## Consequences
## Alternatives Considered
Template changelog
schema_version | Date | Change | Migration |
|---|---|---|---|
| 1 | 2026-08-06 | Initial definition. | — |
What this page hands off
| To | What |
|---|---|
| AAASM-5601 | Implement the validator: the parsing contract, the field reference, the 15 cross-field rules and the freshness thresholds are intended to be sufficient with no further decisions. If a rule needs judgement to implement, that is a defect in this page — report it rather than choosing. Also: replace the hand-maintained area id table with a generated one, by adding a stable id to each row of hub-components.toml and to the five literal rows in generate_hub_components.py. |
| AAASM-5610 | Apply metadata blocks to existing hub content. This page carries the only block today. Expect the three 🗺️ Planned areas — cloud, enterprise, operations — to take the rule 4 path with platforms: [], and expect product-promise.md to need a block plus a rule 13 review. |
| AAASM-5621 / ADR 0034 | Precedence between the three axes, waivers, and cross-repository adoption records. The scope of forbidden design 12’s coining clause is settled — it is claim-axis only — and is applied here, not deferred. |
| AAASM-5531 / AAASM-5600 | The capability/evidence manifest has landed and capability_ids is now validated wherever a page declares it (docs/scripts/validate_capability_ids.py). Making the field required — the schema_version: 2 half of this hand-off — is still open, and belongs with AAASM-5610’s rollout rather than being forced here. |
Last reviewed: 2026-08-06 — AI Agent Assembly Team
Last updated: 2026-09-07 by AI Agent Assembly Team
Audiences, jobs-to-be-done and information requirements
This page is for anyone deciding what a page is for — its author, its reviewer, or whoever is designing the navigation it will sit in. It names the readers this product writes for, the job each of them arrives to finish, and the information a page must supply for that job to complete.
It exists because a surface can be accurate, well-owned and correctly bounded and still fail: the reader who needed it could not tell it was theirs, or reached it and found the one fact their decision turned on was somewhere else. Ownership answers who decides this fact. This page answers the different question of who needs it, to finish what.
It is an input to an information architecture, not a taxonomy for its own sake. Every audience below has at least one job, every job ends in a named decision or action, and every information requirement is traced to a surface that satisfies it today or recorded as a gap. The gaps are the operative output — they are the page-shapes that do not exist yet.
What governs this page
This page is downstream of five merged artifacts. It adds no claim to any of them and restates none of their definitions.
| Source | What it supplies | Where |
|---|---|---|
| Page standards | The audience enum this page’s audiences are, the four disclosure levels, and the metadata contract every page named below must satisfy | page-standards.md |
| Content-layer ownership | The L0–L6 layer model, each layer’s primary audience, and the rule that a derivative may narrow but never widen | content-ownership.md |
| ADR 0034 — one product truth | The T1–T7 authority hierarchy, hand-off 7’s three-axis ruling, and the reviewer classes that sign off a boundary | ADR 0034 |
| Product promise & message hierarchy | The one approved promise, the default-posture table, and the Provisional list | product-promise.md |
| Risk scenarios | The flagship story and three supporting threats, with the Tier 1 / Tier 2 publication gate | risk-scenarios.md |
Ticket references are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
What this page does not decide
- Which pages exist, and where they sit. AAASM-5594 designs the product-site and Docs Hub sitemaps from this model. This page supplies the requirements and the gaps; it does not draw the tree.
- Any product claim. Where an audience’s requirement names a product fact, the
fact is cited to
product-promise.md,risk-scenarios.mdor a manifest row. Nothing here is a new capability statement, which is why this page carriesdescribes_capability: false. - Page metadata rules.
page-standards.mdowns the block, the field reference and the fifteen cross-field rules. This page consumes itsaudiencekey; it does not extend it. - Ownership of any content type. That is
content-ownership.md’s canonical-source table.
The reader axis, and why this page coins nothing
This is the part most likely to go wrong, so it is stated before the model rather than left implicit in it.
A reader vocabulary already exists, and this page does not own it.
page-standards.md’s audience key takes one of six values —
evaluator, developer, operator, security-engineer, contributor, auditor —
and those six are the audiences below. Not analogues of them, not a refinement of them:
the same six words, used for the same subject. Publishing a second reader vocabulary
beside that key would create exactly the two-vocabularies defect this programme exists
to eliminate, and it would do it on the one key an information architecture routes by.
The six are also already in
content-ownership.md’s
layer table, in its Primary audience column, which reads in full: anyone assessing
the company at L0; evaluators, buyers, technical leaders at L1; teams, security
engineers, operators at L2; application developers, operators, contributors, security
researchers at L3; developers who want to see it run at L4; a visitor who landed on
the repo at L5; contributors, auditors at L6. That column is the prose form of the
same axis, and this page reconciles to it rather than beside it — its security
researchers are security-engineer, and its buyers and technical leaders are
evaluator.
Three of its entries are not reader classes, and none of the three maps to an audience
value on its own:
- L0’s anyone assessing the company is an arrival, and it is outside what this product writes for entirely — the company site’s reader, not this product’s.
- L5’s a visitor who landed on the repo is also an arrival, but not out of scope:
they become a
contributor, adeveloperor anevaluatoras soon as they have a job, which is what a README’s “where its documentation is” line exists to decide. - L2’s teams is a collective, not a role — a team is some mixture of
operator,security-engineeranddeveloper, which is why the L2 cell names the first two alongside teams rather than instead of it. It routes to whichever member holds the job in hand, and no page targets it directly:audienceis a list precisely so a page can name the two or three values a team comprises.
None of the three is a seventh audience.
The axis this sits on, and the one rule that binds it
ADR 0034 hand-off 7 fixes three vocabularies, each ranging over a different subject — an action takes an ADR 0033 §6 claim term, a documentation area takes a maturity label, a product in the portfolio takes a lifecycle value — and rules that no axis may be applied to another’s subject.
The reader axis sits outside all three. Hand-off 7 counts “three vocabularies in
total”, and that count is ADR 0034’s to keep — this page is not adding a fourth row to
someone else’s table, it is observing that the audience enum is a vocabulary hand-off
7 does not range over, and asking what follows. Its subject is a person
arriving at a page, which is not an action, not a documentation area and not a
portfolio product. Its vocabulary is the audience enum, its owner is
page-standards.md, and hand-off 7’s rule applies to it in both
directions:
- No term from another axis is written about a reader here. No audience below
carries an availability value, a §6 claim term, a maturity label or a lifecycle
value. A reader is not
Unmeasuredand an audience is not🧪 Release candidate. - No term is coined on the claim axis. Forbidden design 12’s coining clause is scoped to the claim axis alone; this page adds nothing to any axis, so the question does not arise. Every product fact cited below is quoted or linked from a page that already owns it.
The one thing this page does add is the job and the information requirement, and neither is a vocabulary. A job is a sentence about a reader’s intent; a requirement is a sentence about what a page must contain. Neither ranges over an action, an area, a product or a reader class, so neither collides with anything.
Crosswalk: seven role labels, six audience values
The parent scope names seven roles. They map onto six audience values, and the
collapse is deliberate rather than a rounding error.
| Role as named in the parent scope | audience value | Note |
|---|---|---|
| Executive / Evaluator | evaluator | Job EV1 |
| PM / Engineering Leader | evaluator | Job EV3 — the same value, a different job |
| Security / Risk | security-engineer | Includes L3’s security researchers |
| QA / Assurance | auditor | L6’s audience in the layer table |
| Platform / SRE | operator | |
| Application / AI Developer | developer | |
| Maintainer / Contributor | contributor |
Two roles share evaluator, and the metadata key cannot tell them apart. An
executive deciding whether to trial at all and an engineering leader deciding what to
sequence want different pages, and audience: [evaluator] routes both to the same
place. The distinction is real and it is carried by user_job, not by a seventh
enum value — a page states which of the two it serves in the one field
page-standards.md gives it — the one field that can express it,
though that page reserves user_job for stating a page’s job generally, not for
separating two audiences that share a value. Coining a seventh
value would be an edit to someone else’s enum, made from a page that does not own it,
to express something the existing schema already expresses.
That is a recorded limitation rather than a silent one: an automated router keyed on
audience alone cannot separate EV1 from EV3. If a sitemap needs to, it reads
user_job. Whether the enum should gain a value at schema_version: 2 is
page-standards.md’s decision, and this page hands it the
evidence rather than pre-empting it.
How to read an audience entry
Each of the six carries the same seven fields, and each field exists because a sitemap needs it.
| Field | What it answers | Why an IA needs it |
|---|---|---|
| Who | Which real roles this value covers | Stops two readers being served one page by accident |
| Arrives knowing | The context the reader already has | Fixes the disclosure level an entry page opens at |
| Must be able to do | The capability the reader leaves with | The test a candidate page is judged against |
| Jobs | Each job, and the decision or action it ends in | The unit a page is designed around |
| Never hidden | What must be reachable from their route, without exception | The content a navigation redesign may not bury |
| Belongs elsewhere | Content this reader is not the audience for | The anti-persona, stated as content rather than as a person |
| Entry · next · escalation | The three-step route | The spine of the sitemap branch |
Belongs elsewhere is an anti-persona in its operative form. Naming a person nobody is writing for is unfalsifiable; naming the content that must not be on a reader’s entry page is checkable against a candidate sitemap in a single pass. Where a role is genuinely out of scope for the whole product, it is recorded once at the end rather than repeated six times.
On the identifiers
Job ids are two letters and a digit (EV1, SE1, AU1, OP1, DV1, CO1); gaps are
GAP-n. Neither shape is arbitrary. The capability manifest’s rows are a single letter
and a digit, and the letter is its domain — S sdk, H host_action, N network,
M mcp, L devtool_launch, C credentials, I identity, G degraded_mode, P
platform, eighty rows in nine series. This page cites some of them by id, so a job
called S1 and a manifest row called S1 would collide on exactly the identifier a
checking reader follows. risk-scenarios.md lettered its scenarios
F and T1–T3 for the same reason. Where a single-letter id appears below, it is the
manifest’s and is named as such.
The letter is the domain, not the owning component, and G is where that distinction
bites. Every G row is domain: degraded_mode — the series is about what happens when
a control cannot run, not about the gateway. Only three of the eleven are owned by
aa-gateway — G8, G9, G10; the other eight belong to aa-runtime (five),
aa-proxy (two) and the SDK (one). This matters for reading the citations below rather
than as a point of order: G9 is this page’s
recurring failure-posture exemplar precisely because it is a degraded-mode row, and a
reader who took G for “gateway” would look for degradation somewhere else and not find
it.
evaluator
Who. Someone deciding whether this product should be adopted, and at what altitude they need the answer. Two roles: an executive or evaluator deciding whether to trial at all, and a PM or engineering leader deciding what to sequence and what to tell a stakeholder is not yet available.
Arrives knowing. The category, and a concrete worry — an agent did something, or plausibly could. Sometimes a competitor. Does not know the product’s mechanisms, its routing model, or its platform position, and should not need to.
Must be able to do. Reach an accurate account of what the product decides, on which paths, what is on by default and what it leaves uncovered, without opening an ADR; and separate what ships today from what is decided but not built.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
EV1 | Decide whether this product is worth a trial | A trial started, or a recorded reason not to |
EV2 | Decide whether one stated capability meets one stated requirement | Met · met with a named limit · not met |
EV3 | Decide what to sequence, and what to tell a stakeholder is not available yet | A plan whose gaps are named rather than assumed |
Never hidden. The boundary clause, on the same screen as any headline that needs it
(product-promise.md). The default posture — a capability that
exists but is off is a different product from one that is on. The area’s maturity
label. The channel and platform position. That an approval hold has no shipped
operator surface today, which
product-promise.md carries as Provisional.
Belongs elsewhere. Protocol semantics, policy field validation rules, ADR rationale,
per-language API surfaces. An entry page that opens with an architecture diagram has
moved L3 content onto an L1 route; per
content-ownership.md,
that is content at the wrong layer, not a more thorough page.
Entry · next · escalation. Entry: the product website (T6/L1), or this hub’s index —
which carries two evaluator jobs in its Find what you need table but never names the
role, gap GAP-1. Next: product-promise.md level 3 and
source-of-truth.md. Escalation: ADR 0033 §5.3 for the platform
matrix and §6 for the vocabulary, in the core docs.
security-engineer
Who. Security and risk reviewers, and the security researchers
content-ownership.md
names as an L3 audience. One value, because they read the same pages for the same
reason: to find the edge of the boundary.
Arrives knowing. Threat modelling, and that vendor descriptions overstate. Arrives sceptical, which is the correct posture and should be rewarded rather than managed.
Must be able to do. Determine the trust boundary and its enumerated bypasses; find the failure posture of each control, including the ones that fail open; establish what an absent or degraded control reports; and file a vulnerability report at the right address.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
SE1 | Decide whether this boundary is acceptable for a named class of agent traffic | An approval or a refusal for one deployment |
SE2 | Determine what a quiet result means | An uninspected action read as Unmeasured rather than as clean |
SE3 | Report a vulnerability | A report filed against the owning repository’s SECURITY.md |
Never hidden. The bypass catalogue. The failure posture of every control cited,
including the silent fail-open on the budget store that
risk-scenarios.md records as row G9. That the audit chain is
tamper-evident rather than signed, and that emission is best-effort. The platform
matrix, in both directions. Understating it is a defect too — ADR 0034 grades it below
broadening “because it is less dangerous, not because it is acceptable”, and records
that understatements in this programme were introduced while correcting
overstatements, with at least one reaching main. A security reader is the audience
an understated boundary misleads most.
Belongs elsewhere. Install ergonomics, SDK API surface, positioning. A security reader does not need a getting-started path on their entry page and will read one as evasion.
Entry · next · escalation. Entry: this hub’s security-model.md
— which carries a superseded model today, recorded as gap GAP-5 below. Next: the
core threat model and ADR 0033. Escalation: the capability manifest rows and the
verification reports in the core repository.
auditor
Who. QA and assurance readers: anyone whose job is to check a published statement against something, rather than to build or to buy.
Arrives knowing. How to evaluate evidence. Does not know the codebase and should not have to read it to establish what backs a sentence.
Must be able to do. Take any published claim and reach the evidence it rests on, or establish that there is none and record that; and interpret a verification result for what it establishes rather than for what it suggests.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
AU1 | Find the evidence behind one published claim | A cited row, or a recorded gap |
AU2 | Verify an audit record | A result read as integrity of the entries present, not completeness of the log |
AU3 | Decide whether a demonstration may be published as evidence | Tier 1 published, or Tier 2 withheld |
Never hidden. Which statements are Provisional, and why. The Tier 1 / Tier 2
publication gate in risk-scenarios.md. That a passing chain
verification does not establish the log is whole. That absence of a finding is a fact
about the observer.
Belongs elsewhere. Positioning and conversion copy. An auditor arriving at a marketing page has been misrouted, and no amount of accuracy in that page fixes it.
Entry · next · escalation. Entry: none on this hub today — the index does not
mention this reader (gap GAP-1), and an entry alone would not be enough, because the
evidence it would route to is not published either (gap GAP-4). Next: the
determinations in risk-scenarios.md, which is the closest thing
to a claim-to-evidence route currently published. Escalation:
capability-manifest.yaml
and the verification reports — both L6, and
content-ownership.md
states that nothing in L6 is a reader-facing page, which is the shape of the gap.
operator
Who. Platform engineers and SREs: the people who put the product in front of an agent and keep it there.
Arrives knowing. How to run a service. Does not know that routing is a thing they do per agent and per launch, which is the single most consequential thing this audience learns.
Must be able to do. Route an agent on one host; install the right artifact for their platform from a channel that carries it; observe what the stack recorded; and work out why a control did not fire.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
OP1 | Route an agent through the product on one host | A governed launch |
OP2 | Decide what to install, on which platform, from which channel | An install performed |
OP3 | Work out why a control did not fire | A corrected configuration, or an accepted bound |
OP4 | Stand up a limited-function self-hosted stack for evaluation | A running stack |
Never hidden. That routing is per agent and per launch, so an agent nobody routed
is outside everything. The per-channel and per-platform position — the manifest’s
released_channels and released_platforms are per row, and
risk-scenarios.md records that the proxy reaches macOS through
crates.io only. Which controls are off until configured. The failure postures, so a
silent fail-open is not discovered during an incident.
Belongs elsewhere. Production orchestration commitments. Helm, Terraform and
Kubernetes are a research question under current project policy, not committed work,
and open-core-boundary.md is where the self-host scope is
stated. A page that reads as a production deployment guide has made a commitment the
product has not.
Entry · next · escalation. Entry: this hub’s Getting Started section — whose
two pages are both 🗺️ Planned, gap GAP-2. Next:
docker-containers.md and
self-host-observability.md, which describe shipping
behaviour but are filed under Operations rather than on the entry route.
Escalation: the core quick-start and CLI reference.
developer
Who. Application and AI developers integrating the product into an agent they are building.
Arrives knowing. Their framework and their language. Wants working code, and will judge the product on how quickly they get some.
Must be able to do. Add a policy checkpoint in their language; choose an SDK mode knowing what each one does; and find a runnable integration for the framework they are actually using.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
DV1 | Add a policy checkpoint to an agent in my language | Code that runs and reaches a decision |
DV2 | Choose an SDK mode | An explicit choice between the advisory default and the check-capable mode |
DV3 | Find a runnable integration for my framework | An example running locally |
Never hidden. That the SDK is advisory, and that a policy refusal blocks a wrapped
tool only in the check-capable mode — product-promise.md’s
default-posture table carries both. That an unadapted framework, or a call that does
not go through the framework’s dispatch, is outside the wrapper: the manifest’s S11
and S10 carry exactly those two, and S12 the raw HTTP, subprocess and filesystem
class — all three language: [python, node, go], which is what makes them the right
citation for an audience that spans three languages. A quick-start that omits them has
widened the claim by dropping a precondition. Per-language rows say more but say it
narrowly: S1’s known_bypasses list the same items plus not calling
init_assembly(), and S1 is Python — Go’s S8 has a different list.
Belongs elsewhere. The threat model, the deployment matrix, positioning. A developer needs the boundary, but as a precondition on their own code rather than as a security chapter.
Entry · next · escalation. Entry: README.md’s SDKs & components
table, or documentation.md — both branch by language, and neither
is in the sidebar (gap GAP-7). Next: the SDK documentation mounted at /python-sdk/,
/node-sdk/ and /go-sdk/ by the aggregation pipeline, which is where the first
checkpoint lives. Escalation: the core API reference and the runnable examples at L4.
contributor
Who. Maintainers and contributors, in any repository in the org, including the coding agents working under the org’s rules.
Arrives knowing. The repository they are in. Does not know the cross-repository truth hierarchy, and will otherwise fix a defect in the place they noticed it.
Must be able to do. Classify a fact and find its canonical owner; choose a sanctioned reuse pattern; write a page that satisfies the metadata contract; and route a correction to the source before the derivative.
Jobs.
| Job | Stated as a job | Ends in |
|---|---|---|
CO1 | Decide where a fact belongs before writing it | A layer and a canonical owner named in the ticket |
CO2 | Write a page that conforms | A page carrying a valid metadata block |
CO3 | Route a correction | A pull request against the canonical source first |
CO4 | Decide whether a change is a material truth change | The right reviewer class requested |
Never hidden. That depth is not a defect — no rule in this programme may be cited to thin a component’s documentation. That understating is a defect too — graded below broadening by ADR 0034, but a defect, and a contributor trimming for brevity is the commonest way one gets introduced. That an ownership dispute is a decision, not an edit, and stops rather than resolves inside a content pull request.
Belongs elsewhere. Positioning copy and conversion paths. A contributor reading those is reading the wrong layer for their job.
Entry · next · escalation. Entry: page-standards.md. Next:
content-ownership.md
and
claim-vocabulary.md.
Escalation:
ADR 0034
for precedence, waivers and the reviewer classes.
Roles this product does not write for
Recorded once rather than repeated as six anti-personas, because each is out of scope for every surface rather than for one route.
| Not an audience | Why | Where such a reader goes |
|---|---|---|
| Someone evaluating the company rather than the product | L0’s job, and L0 must not carry a per-capability status or a platform claim | horonomy.dev |
| A reader looking for agent-building guidance | This product governs an agent; it does not help write one. A page that teaches agent construction has widened the product’s subject | Framework documentation |
| A procurement or compliance reader wanting an SLA, a region or a certification | Planned, not available; asserting any of it is a managed-service claim bounded by the checklist | saas-claim-publication-checklist.md and source-of-truth.md |
A reader of the private cloud or agent-assembly-enterprise internals | Outside the public content boundary; paraphrasing does not make it publishable | The public ticket |
Information requirements
One row per (audience, job, requirement). What a page must supply is the contract: if a candidate page does not carry it, the job does not complete on that page. Where it is satisfied today is this model applied once to the surfaces that exist, which is what makes it a review instrument rather than a wish list.
Status values: ✅ satisfied — a published page carries it on that audience’s route; ◐ partial — a published page carries it, but not on that route, or not in full; ✗ gap — nothing published carries it.
Requirements for evaluator
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-EV1-a | EV1 | The one approved promise, with its boundary clause on the same screen | product-promise.md | ◐ partial (GAP-1, GAP-8) — filed under About, and no evaluator route reaches it |
IR-EV1-b | EV1 | One concrete story of a decision the product made, with its determination | risk-scenarios.md | ✅ satisfied |
IR-EV1-c | EV1 | What is on by default, as a table rather than as prose | product-promise.md level 3 | ◐ partial (GAP-1, GAP-8) — one satisfier, and it is not on the route |
IR-EV2-a | EV2 | Per capability: whether it ships, on which channel and which platform | manifest released_channels / released_platforms (L6) | ✗ gap GAP-3, GAP-4 |
IR-EV2-b | EV2 | Per capability: whether anything reaches it by default | manifest default_state (L6); product-promise.md for the headline set | ◐ partial |
IR-EV2-c | EV2 | The stated limit that changes what may be relied on | product-promise.md, risk-scenarios.md known-boundary blocks | ✅ satisfied |
IR-EV3-a | EV3 | The maturity of each documented area | source-of-truth.md | ✅ satisfied |
IR-EV3-b | EV3 | What is decided but not built, with its ticket and no capability claim | scattered Planned statements; no roadmap surface | ✗ gap GAP-6 |
IR-EV3-c | EV3 | The open-source / managed split, so a plan can be split along it | open-core-boundary.md | ✅ satisfied |
Requirements for security-engineer
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-SE1-a | SE1 | The trust boundary, in the current architecture rather than a superseded one | core ADR 0033 and the core security section | ◐ partial — the hub entry page is superseded (GAP-5) |
IR-SE1-b | SE1 | The enumerated bypasses, published rather than implied | risk-scenarios.md; manifest known_bypasses | ◐ partial (GAP-4) — complete only at L6 |
IR-SE1-c | SE1 | The failure posture per control, including the fail-open ones | manifest failure_posture; G9 in risk-scenarios.md | ◐ partial — one worked instance published, the rest at L6 (GAP-4) |
IR-SE1-d | SE1 | The platform matrix, stated in both directions | core ADR 0033 §5.3; restated on the hub by product-promise.md — which carries the macOS row in both directions explicitly — and by README.md’s layer 2/3 notes | ◐ partial — restated in prose on two pages, but as neither a matrix nor anything on the security-engineer route (GAP-1) |
IR-SE2-a | SE2 | That an uninspected action is reported as Unmeasured, never as clean | product-promise.md, risk-scenarios.md | ✅ satisfied |
IR-SE2-b | SE2 | What a passing chain verification does and does not establish | product-promise.md | ✅ satisfied |
IR-SE3-a | SE3 | The vulnerability reporting address for the repository in question | each repo’s SECURITY.md, falling back to the org default | ◐ partial (GAP-1) — no hub route names it; a sweep for SECURITY.md, security@ and report a vulnerability returns zero on every hub page, against a control (security) that hits on 14 of the 23 |
Requirements for auditor
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-AU1-a | AU1 | A published route from a claim to the row or record that backs it | nothing published; the manifest is L6 | ✗ gap GAP-4 |
IR-AU1-b | AU1 | Which claims are Provisional, and the ticket that would close each | product-promise.md | ✅ satisfied |
IR-AU2-a | AU2 | What the verification command establishes, and its two negative results | product-promise.md | ✅ satisfied |
IR-AU3-a | AU3 | The Tier 1 / Tier 2 gate, and which tickets lift it | risk-scenarios.md | ✅ satisfied |
IR-AU3-b | AU3 | For a managed-service claim, the register that bounds it | saas-claim-publication-checklist.md | ◐ partial — interim, and managed-service only |
Requirements for operator
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-OP1-a | OP1 | That routing is performed per agent and per launch | product-promise.md level 2 step 1 | ◐ partial (GAP-1) — not on the operator route |
IR-OP1-b | OP1 | The launch preconditions in full, none dropped | core quick-start; risk-scenarios.md governed-path fields | ◐ partial |
IR-OP2-a | OP2 | Which artifact reaches which platform, from which channel | manifest (L6); compatibility.md carries versions only | ✗ gap GAP-3 |
IR-OP2-b | OP2 | An install route that does not begin with an unavailable page | docker-containers.md | ✗ gap GAP-2 on the entry route |
IR-OP3-a | OP3 | Which controls are off until configured | product-promise.md level 3 | ◐ partial (GAP-8) |
IR-OP3-b | OP3 | The failure postures, including the ones that produce no decision-path signal | risk-scenarios.md for G9; manifest for the rest | ◐ partial (GAP-4) |
IR-OP4-a | OP4 | The scope of a limited-function self-host, and what it excludes | open-core-boundary.md, docker-containers.md | ✅ satisfied |
IR-OP4-b | OP4 | What the stack records, and how to read it | self-host-observability.md | ◐ partial (GAP-2) — filed off the entry route |
Requirements for developer
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-DV1-a | DV1 | A language-specific first checkpoint, reachable from this hub | the SDK docs, mounted and listed — README.md’s SDKs & components table and documentation.md both branch by language | ◐ partial (GAP-1, GAP-7) — reachable in two hops from two index pages, absent from the sidebar, and no checkpoint on the hub itself. A sweep for init_assembly, WrapTools and the three install commands finds none on any hub page, against a control that hits |
IR-DV1-b | DV1 | The initialisation preconditions the wrapper depends on | SDK docs; manifest S1 preconditions (AA_AGENT_ID set) | ◐ partial |
IR-DV2-a | DV2 | That the SDK is advisory, and what the check-capable mode changes | product-promise.md level 3 | ◐ partial (GAP-1, GAP-8) — not on a developer route |
IR-DV2-b | DV2 | What sits outside the wrapper, stated as preconditions on their code | manifest S1 known_bypasses, and rows S10–S12 (L6) | ✗ gap GAP-4 |
IR-DV3-a | DV3 | A runnable integration per framework, and a way to choose between them | the examples repository (L4), routed from README.md’s Runnable examples section, its Find what you need table and its next-step call to action, and again from documentation.md | ◐ partial — well routed; the missing half is the chooser, since those routes name the framework dimensions but offer no way to pick between them |
Requirements for contributor
| ID | Job | What a page must supply | Where it is satisfied today | Status |
|---|---|---|---|---|
IR-CO1-a | CO1 | The canonical owner per content type | content-ownership.md | ✅ satisfied |
IR-CO1-b | CO1 | The four sanctioned reuse patterns, and when each applies | same | ✅ satisfied |
IR-CO2-a | CO2 | The metadata contract and a copyable template per page type | page-standards.md | ✅ satisfied |
IR-CO2-b | CO2 | Which audience and job a page is being written for | this page | ✅ satisfied |
IR-CO3-a | CO3 | Where a correction goes first, as an ordered procedure | content-ownership.md | ✅ satisfied |
IR-CO4-a | CO4 | The reviewer classes, and what counts as a material truth change | ADR 0034 Decision 9 | ✅ satisfied |
The gaps
This is the list AAASM-5594 turns into pages. Each row names the requirements it blocks and the page-shape it implies; none of them is a request to move an existing page without a job to justify it.
Two of these gaps are bounded by something that already exists, and the bound is the point. A gap that overstates an absence sends 5594 to build a page the hub already has — which is worse than missing the gap, because the duplicate then competes with the original for the same reader. Where a route exists and is merely thin, the row says so and narrows the ask to the residue.
The relation below is invertible, and that is a property worth relying on. Every
requirement id in a Requirements it blocks cell names that gap back in its own
Status cell, and no status cell names a gap that does not claim it — 24 pairs, checked
mechanically in both directions. So “is every gap closed by this sitemap?” is a set
comparison over the two tables, not a reading of them. It is deliberately not a
separate machine-readable block: page-standards.md and AAASM-5601
own structured page data, a second block format invented here would be exactly the
coin-a-vocabulary-someone-else-owns defect this page argues against, and 5601’s validator
would not know to check it. Two Markdown tables that parse cleanly give 5594 the same
guarantee without the new format.
| # | Gap | Requirements it blocks | Page-shape implied |
|---|---|---|---|
GAP-1 | No surface routes all six audiences; the index covers two and sends two away. README.md’s “Who this documentation is for” names teams, security engineers, and operators — the same three roles as content-ownership.md’s L2 cell, though not word for word — so it covers operator and security-engineer. Its next sentence routes “a developer who wants to contribute or integrate at the code level” off-site to the core docs, which under this page’s own crosswalk addresses both developer and contributor — addressed, but deliberately not entered here. That leaves evaluator and auditor unmentioned, and no per-audience route beyond that one paragraph. The index does carry two evaluator jobs in its Find what you need table — Compare AI Agent Assembly to other tools (EV1) and Know what is open source vs. paid (EV3) — but job-addressed rows are not a role route, which is the distinction this gap is about. The product website publishes four pages and a blog and routes by none of them | IR-EV1-a, IR-EV1-c, IR-SE1-d, IR-SE3-a, IR-OP1-a, IR-DV1-a, IR-DV2-a | A per-audience entry for the six, on both L1 and L2 — extending the index’s existing role paragraph, not replacing it |
GAP-2 | The hub’s Getting Started section contains only 🗺️ Planned pages. Both entries are labelled Coming soon, and source-of-truth.md marks the Operations area 🗺️ Planned with a managed-service page as its Where to read cell — while two pages describing shipping behaviour sit under Operations and are not named by that row | IR-OP2-b, IR-OP4-b | An operator entry that starts from what ships |
GAP-3 | No per-platform install position is published on this hub, and the channel picture is partial. compatibility.md pairs versions across the four repos, names npm and PyPI, and does name GitHub Releases (“core cuts GitHub Releases”, plus a /releases link) — so the absence is not total. But it names no Homebrew, crates.io, install-script or GHCR position, and carries no platform at any point: a whitespace-normalised sweep for linux, macos, windows, x86_64 and aarch64 returns zero hits, against a positive control that hits on the same normalised text. The per-row facts live in the manifest’s released_channels and released_platforms | IR-EV2-a, IR-OP2-a | A channel-and-platform matrix at L2, generated from the manifest and keyed on its channel vocabulary rather than a hand-written list — see the GHCR hand-off below |
GAP-4 | The evidence layer has no reader-facing surface. capability-manifest.yaml exists with a schema, a validator and a CI gate, and verification-reports/** holds the records — but content-ownership.md states nothing in L6 is a reader-facing page, and ADR 0034’s T3 approved-claims registry does not exist yet | IR-AU1-a, IR-DV2-b, and the L6-only half of IR-EV2-a, IR-SE1-b, IR-SE1-c, IR-OP3-b | A generated claim-to-evidence surface at L2 |
GAP-5 | The hub’s security entry page carries a superseded model. product-promise.md records that where this hub still carries the older framing, ADR 0033 wins, and names the migration tickets — but a reader arriving at that page first never sees the note | IR-SE1-a | A security entry rewritten against ADR 0033, not a note added to the old one |
GAP-6 | No roadmap surface exists. No file named for one is present in this repository, the product website or the core repository, checked with a positive control in the same sweep; ADR 0034 hand-off 4 assigns the owner, so the owner currently owns an empty surface | IR-EV3-b | A bounded forward-looking page at L1, in the admissible forms only |
GAP-7 | The language route exists; what is missing is a first checkpoint and a sidebar entry. Two hub pages branch by language — README.md’s SDKs & components table links /python-sdk/, /node-sdk/ and /go-sdk/, and documentation.md is a router that does the same — so a developer entry that branches by language already exists twice and must not be built again. The residue is narrower: the mounts are reachable only from those two pages’ bodies and appear nowhere in the sidebar, and no hub page carries a language-specific first checkpoint | IR-DV1-a | A sidebar route to the mounts; the checkpoint itself belongs in the SDK docs at L3, not here — see the note below |
GAP-8 | The default-posture table has exactly one satisfier, and it is filed under About. Defaults are stated at level 3, whose bound page-standards.md sets as “Must state defaults and non-coverage” — and level 3 is a must for four of the five page types (product, guide, reference, architecture), not for product alone. No hub page on an evaluator or operator route carries level 3 today | IR-EV1-a, IR-EV1-c, IR-OP3-a, IR-DV2-a | A level-3 surface on those routes. product carrying levels 1–3 is one admissible shape, not the required one |
Why GAP-7’s missing checkpoint is not a page this hub should write
IR-DV1-a asks for a language-specific first checkpoint reachable from this hub, and
the temptation is to close it by writing one here. That would be the wrong fix twice
over. content-ownership.md puts per-language API surfaces at L3 and forbids L2 a
reference of its own for anything a component owns; and this repository’s own project
instructions at
.claude/CLAUDE.md
say the hub orients readers toward component docs and does not re-author their install
steps or API surface, “which would only drift”. A checkpoint written here would
be a derivative reproducing its source at the same depth — the defect
page-standards.md names when it forbids a summary that replaces
its source.
So the closable half of GAP-7 is the route, not the content: the SDK mounts belong
in the sidebar. The checkpoint itself is the SDK docs’ to carry, and if it is thin there,
that is an L3 ticket against the SDK in question rather than anything 5594 can draw.
Four hand-offs, not gaps
Recorded here because they were found while deriving the gaps and would otherwise be lost, but none is this page’s to fix and none blocks a requirement.
-
GHCR is a published channel that the manifest’s
released_channelsvocabulary does not contain.docker-containers.mdstates that five multi-arch images are published toghcr.io/ai-agent-assembly; the manifest’sreleased_channelsvalues across all eighty rows arecrates_io,github_release,homebrew,install_script,pypi,npmandgo_modules— eight distinct values counting the sentinelnot_applicable, which is not a channel and must not become a column — with no Docker or GHCR value.GAP-3’s page-shape is a matrix generated from the manifest, so a matrix built faithfully from that vocabulary would ship without a GHCR column while the hub separately documents the images. Whether GHCR belongs in the manifest is the manifest’s owner’s call, not this page’s, and is now tracked as AAASM-5680 — but 5594 needs to know before it generates anything. -
glossary.mdalso carries the superseded model.product-promise.mdnames two pages still carrying the older framing —security-model.mdandglossary.md.GAP-5is scoped to the security entry page because that is the one on asecurity-engineerroute, but a sitemap treatingGAP-5as the complete list of superseded surfaces will miss the glossary, which is owned separately by AAASM-5658. -
page-standards.md’s note that the capability manifest has not started is now stale.capability-manifest.yamlis present in the core repository under AAASM-5531 with a schema, a semantic validator and a CI gate. That affects the reserved status ofcapability_idsand theplatforms[]hand-off, both of which that page marks as pending AAASM-5531. It ispage-standards.md’s edit to make, at whatever schema version it decides. -
The
evaluatorvalue cannot separateEV1fromEV3. Recorded above; the evidence is handed topage-standards.mdrather than resolved by coining a value here.
Using this model
To evaluate an existing page
Answer four questions in order. The first two are the ones that catch a misplaced page.
- Which audience value, and which job? Name one of the six and one job id. A page
that serves no job on this list either has an unnamed audience — in which case name
it — or does not need to exist. This is the same question
page-standards.md’saudienceanduser_jobkeys ask, so a conforming page has already answered it. - Does it carry every requirement for that job? Walk the rows for that job id. A missing requirement is either a defect in the page or a link the page must add.
- Does it carry anything from belongs elsewhere? Content for another audience on this reader’s route is the commonest cause of a page that is accurate and still unusable.
- Is anything from never hidden absent? That list has no exceptions, and its items are the ones a redesign removes first because they are the least attractive.
To evaluate a proposed navigation
- Every audience has an entry. Six values, six routes. A value with no entry means a reader with no way in.
- Every route reaches its escalation in three steps. Entry, next, escalation. A fourth step is a route nobody finishes.
- No route opens on a
🗺️ Plannedpage. GapGAP-2is what that looks like when it happens. - Every gap above is either closed by a page in the proposal, or is carried forward as a named open item. A sitemap that silently drops one has not resolved it.
- No route requires a reader to reach L6 to finish their job. Where it does today,
that is gap
GAP-4and the fix is a surface, not a deep link.
How this page meets its acceptance criteria
| Criterion | How it is met |
|---|---|
| Every audience has an explicit job-to-be-done and information contract | Six audience sections, each with a Jobs table whose every row ends in a decision or an action, and an information requirements block keyed by job id. Twenty jobs, forty requirements, each with a named satisfier or a gap, and the gap relation invertible in both directions |
| The model distinguishes non-developer technical readers from application developers and maintainers | security-engineer, operator and auditor are the non-developer technical readers and each carries its own jobs, its own never hidden list and its own belongs elsewhere boundary; developer and contributor are separate values with separate routes. The crosswalk states which of the parent scope’s seven roles lands on which value, including the two that share evaluator and the reason they are separated by user_job rather than by a coined enum value |
| The model is usable to evaluate current pages and proposed navigation | Using this model gives both checklists, and the Where it is satisfied today column is that evaluation already applied once to every published surface — which is what produced the gaps. Every absence asserted there was re-derived with a whitespace-normalised, block-markup-stripped probe carrying a positive control, because a phrase that wraps a line or crosses a table cell is invisible to a per-line search and reads as a gap that is not there |
| Website, Docs Hub, SaaS docs and project docs owners approve the boundaries | Not something a page can assert about itself. The boundaries are stated in the owning classes’ own terms — L1/T6 for positioning and the roadmap, L2/T5 for routing and maturity, L3/T4 for architecture and semantics, and the SaaS claim publication checklist for managed-service claims — so approval is the pull request’s, under ADR 0034 Decision 9, from truth-owner-website, truth-owner-docs-hub and truth-owner-core |
What this page hands off
| To | What |
|---|---|
| AAASM-5594 | The eight gaps and the forty information requirements, as the input to the product-site and Docs Hub sitemaps, related by 24 pairs that invert. The two navigation checklists are intended to be sufficient to review a proposed tree without further decisions from this page. Read GAP-1 and GAP-7 before drawing anything: both are bounded by routes that already exist on README.md, and the ask is the residue, not a new entry page |
| AAASM-5585 · AAASM-5587 | The evaluator entry and its requirements, including the default-posture table gap GAP-8 — whose constraint is a level-3 surface on the route, not a page of any particular page_type |
| AAASM-5596 · AAASM-5608 · AAASM-5611 | The per-audience never hidden lists, which bound what a rewritten page may drop. GAP-5 covers security-model.md; glossary.md carries the same superseded framing under AAASM-5658 and is not in that gap |
page-standards.md | Two records: that the evaluator value cannot separate EV1 from EV3, and that its AAASM-5531 pending note is now stale |
| AAASM-5680 | That GHCR is a published channel absent from the released_channels vocabulary, which a generated channel matrix would inherit as a missing column. Also that not_applicable is a sentinel in that vocabulary, not a channel, so a generator keying columns on distinct values must exclude it |
Last reviewed: 2026-08-07 — AI Agent Assembly Team
Last updated: 2026-08-07 by AI Agent Assembly Team
Role narratives — Security, Platform, Engineering and Product/QA
This page supplies the four role-specific narrative briefs the product’s evaluator entry pages are built from: Security / Risk, Platform / SRE, Engineering, and Product / QA / Assurance. Each brief carries the seven fields a role surface needs — pain, trigger, intervention, outcome, proof, limitations and next step.
It exists because role pages are the classic place where one product acquires four product truths. Four audiences, four authors, four deadlines, and the security page ends up describing a stronger product than the engineering page describes, without either author ever intending it. Nobody notices, because nobody reads two of them.
So the briefs below are not four independent documents. Every capability sentence in all four is drawn from one numbered shared claim register, and a brief may cite a register entry or stay silent — it may not restate one at a different strength. That is the mechanism, not an instruction to be careful.
It is narrative source material, not a role page. The surfaces that consume it (AAASM-5587) take the wording from here rather than paraphrasing it: a paraphrase is a new claim and carries its own evidence burden.
What governs this page
This page adds no claim to the sources below. Every product fact in it resolves to a row of the capability manifest, and the register records which row.
| Source | What it supplies |
|---|---|
| Capability / evidence manifest (AAASM-5531) | The 80 rows every claim here resolves against — coverage term, decision timing, failure posture, default state, released channels, known bypasses and evidence. governance/capability-manifest.yaml |
| ADR 0033 §6 | The eleven claim terms. Nothing here coins a twelfth. ADR 0033 |
| ADR 0034 | The one-product-truth hierarchy and the rule that an upper layer may simplify a lower-layer fact and may never broaden it. ADR 0034 |
| ADR 0030 §4.1 | The protection-state ladder, and which rung may carry a bypass-resistance claim. ADR 0030 |
| Claim vocabulary | The approved public wording per surface, the prohibited-term rules, and the waiver policy. claim-vocabulary.md |
| Product promise (AAASM-5582) | The single promise every brief below is a rendering of, its clause map, and the Provisional list. |
| Risk scenarios (AAASM-5583) | The flagship story, the three supporting threats, and the Tier 1 / Tier 2 publication gate on prevented-outcome wording. |
| Page standards (AAASM-5595) | Disclosure levels and the metadata contract this page’s own block satisfies. |
Ticket references on this page are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall, and a link checker scores that wall as reachable — which makes the reference look verified when it is not.
Alignment with the audience model. AAASM-5591 defines the reader axis and its six
audience values. Its crosswalk maps the four roles this page serves onto
security-engineer, operator, developer and auditor, and each brief below
records its value. That page had not merged when this one was written, so nothing here
depends on it; the values are recorded so the two do not diverge, and if 5591’s
crosswalk changes, this page follows it rather than the reverse.
What this page does not decide
- The claim terms. ADR 0033 §6 owns them.
- Which claims are true. The manifest owns that. A register entry below is a rendering of one or more rows, never a new measurement.
- The navigation the role pages sit in. AAASM-5594 owns the sitemap.
- The homepage, the Product page or the How It Works page. AAASM-5585 and AAASM-5586 own those, and this page does not rewrite them.
- Visual design, page routes and metadata for the role surfaces. AAASM-5587 owns the implementation.
The one rule this page exists to hold
No brief may state a capability at a strength another brief does not state it at.
Two corollaries, and the second is the one people get wrong:
- A brief may be silent. An engineer does not need the tenant-isolation posture, and omitting it is correct. Silence is not a disagreement.
- A brief may not hedge instead of dropping. If a register entry is too strong for a role page, the page says the weaker true thing — the register’s own bound — or says nothing. It does not restate the stronger thing with a qualifier attached. A hedge reads as the strong claim to every reader who skims, and a hedged strong claim is the same defect as an unhedged one.
Understating is also a defect. The SSRF guard, pre-dial refusal, the fail-closed launch refusal and chain verification all ship, and a brief that hedges them away is wrong in the other direction.
The shared claim register
Sixteen entries. Every capability sentence in all four briefs is one of these, in the wording given here. The Bound column is not optional context — it is part of the claim, and dropping it is the broadening ADR 0034 §2.3 forbids.
Read a row as: this §6 term, about this subject, under this bound, evidenced by these manifest rows.
The invariant that makes a row checkable: an entry’s §6 term must be a term its own cited rows carry in their
coveragefield.Not “compatible with”, not “justified by” — carried. The term is copied off the evidence, never chosen for the sentence and then matched to rows afterwards.
Fourteen entries are subject to the rule and all fourteen hold: RC1, RC2, RC3, RC4, RC5, RC6, RC7, RC8, RC10, RC11, RC13, RC14, RC15, RC16. Two are exempt, for stated reasons rather than by judgement: RC9 carries an ADR 0030 protection state rather than a §6 term, and RC12 is declared no claim with zero rows. Fourteen plus two is the whole register.
Citing rows that carry several coverage terms is not an exemption — it is the rule holding. The test is a subset relation, so an entry names the subset it asserts and leaves the rest unasserted. Six entries do this: RC3, RC5, RC8, RC11, RC15, RC16.
An earlier draft got both halves of that wrong, and in the worst possible place. It said three entries were exempt, then enumerated four, and two of the four — RC8 and RC15 — are not exempt at all: they hold. An author trusting it would have excused from the check the very entries the check covers, inside the statement of the rule written to stop exactly this. Where a total appears beside an enumeration on this page, the enumeration is the answer; if they disagree, the total is the defect.
This is stated as a rule because it is the one that failed in review. RC6 and RC13 originally read
ObservedandEvaluatedover rows carryingunmeasured, and the tell was that the level-4 mapping recordedunmeasuredfor the same two rows — one page, two tables, two answers. Comparing the term column againstcoveragecatches that without reading a word of prose.
| # | §6 term | Claim, in the wording a role page uses | Bound that travels with it | Manifest rows |
|---|---|---|---|---|
| RC1 | Denied before execution | A connection made on a path you routed through Agent Assembly is checked against the destination list you configured and refused before the proxy dials it | The refusal is the proxy’s own local egress configuration, not a control-plane decision. The destination lists are empty by default — this refusal exists because an operator configured it. Linux release artifact; on macOS cargo install aa-proxy is the only route; on Windows there is no local mediation. If the proxy is not in front of the connection, the connection is simply made | N1 |
| RC2 | Denied before execution | Requests to loopback, private, link-local and related address space are refused, including where a public hostname resolves into them | On by default, fails closed, and no configuration relaxes it. It ranges over address space, not over an arbitrary public destination — it does not deliver RC1 and must not be credited with doing so | N2 |
| RC3 | Redacted | On the model-provider hosts Agent Assembly inspects, a recognised credential is removed from the request before it is forwarded | Three built-in hosts, because llm_only defaults on. The default action is redact and forward, not refuse. Recall is bounded by the pattern set — there is no Stripe detector. Model responses on that path are not scanned | N3, C1, C4, C6, G4 |
| RC4 | Denied before execution | An MCP tool call can be checked against your policy by the control plane and refused before the proxy forwards it | The only gateway-bound pre-dial refusal in the product, and it is off by default. It reaches MCP sent as an ordinary HTTP/1.1 POST on an intercepted non-LLM host with a gateway endpoint configured. Tool servers over stdio — the most common setup — SSE and WebSocket have no interception mechanism; Streamable HTTP is recorded as functionally broken rather than merely uncovered | M1, M3; exclusions M2, M4–M9 |
| RC5 | Denied before execution (Python, Go) · Evaluated (Node) · Unmeasured (Node default mode) | A tool call through a wrapped framework seam is checked before the tool body runs | The SDK is advisory by design — a defence-in-depth posture, not the authoritative gate, and an agent that does not call it is not asking. Python raises before the body and fails closed. Go fails closed but requires an explicit WrapTools. Node’s default mode routes the check through an allow-all no-op client, so no refusal is produced there at all; asking for enforcement without a check-capable mode is refused loudly at init rather than silently allowed | S1, S2, S5, S6, S7, S8, S9, S13, G5 |
| RC6 | Unmeasured | Whether a given decision’s record durably reaches the audit chain is not established. The verification tool is real — aasm audit verify-chain ships in the open-source build — but what it proves is the integrity of the entries that are present, not that any particular decision produced one | The manifest’s only row for this subject is the row for what happens when the write fails, and it carries coverage: unmeasured, failure_posture: fail_open and evidence: gap. So the honest term is the row’s own. Everything else about the chain is a bound, not a capability: it is tamper-evident, not immutable and not signed — an unkeyed digest, so anyone able to rewrite the sink can recompute it. The chain head advances before the send and a full channel drops the entry while the call still returns, which makes a dropped entry indistinguishable from a deleted one. An emptied log verifies clean. The proxy writes no local record at all unless its audit path is configured. See the two gaps | G10 |
| RC7 | Unmeasured | Where nothing inspected an action, the record says nothing was inspected — not that it was allowed | Scoped to the action or payload, never to the connection: a host the proxy does not intercept is still adjudicated at CONNECT, so its connection is Observed while its payload is Unmeasured. One live defect runs against this rule today — the CONNECT-level event still records an allow for traffic about to be tunnelled uninspected (AAASM-5637) — so state it as the rule and the open defect together, not as finished behaviour | N5, N10, N12, S10, S11, S12, L6, H1, H6, H7 |
| RC8 | Observed · Detected | On Linux, kernel probes report TLS plaintext, process execution and file activity | No eBPF signal participates in any allow or deny decision. The one enforcing program is an opt-in syscall guard that terminates a confined process after the offending syscall has already run, which is Detected, not Denied before execution. File-I/O probes are x86_64 only. The privileged loader daemon that owns every kernel operation reaches crates.io only — it is absent from the GitHub Release assets, the Homebrew tap and the install script | H2, H3, H4, N13, I4, P1, P2 |
| RC9 | — (ADR 0030 protection state) | The managed launch for Claude Code on macOS is the one path that reaches ADR 0030’s HostEnforced rung | ADR 0030 §4.1 makes HostEnforced the only state that claims bypass resistance, and exactly one manifest row carries it. Two things bound it hard. The rung rests on reading back a root-owned managed-settings file, and whether the tool honours those keys at runtime is unmeasured. And the manifest records the rung as unearned at the published v0.0.1-rc.6 tag — the evidence it rests on postdates the tag. macOS host-level interception itself is integrated, scoped to tool governance only: claim the file, never the enforcement | L1; P3 for the demotion |
| RC10 | Degraded | Where a control was planned and is unavailable, the product reports the planned level and the level actually achieved | Degraded carries both levels or it is not this term. One row reaches it, for eBPF load or attach failure. The reporting half does not close: a degradation is emitted, typed, and rendered nowhere, and an unreadable eBPF policy file fails open silently, raising no degradation event at all | G6; reporting gap G11; silent case G7 |
| RC11 | Denied before execution · Evaluated | Where the control plane is configured and becomes unreachable, the decision path refuses rather than allowing | Fail-closed on the paths that have a gateway: the runtime denies on an unreachable gateway, the proxy refuses to start, and the gateway aborts on a policy that fails to load. The inverse is not symmetric — a runtime with no gateway configured falls through to a local evaluation whose terminal default is allow. Configured-then-broken fails closed; never-configured fails open | G1, G3, G8; the inverse G2 |
| RC12 | Approval required | No claim. | No manifest row reaches this term. The hold itself is real in the gateway path and fails closed on timeout, but no shipped operator surface can answer it, and inside the MCP tunnel a pending decision is downgraded to a refusal, so a human cannot be reached there either. Do not write “held for human review”, “approval workflow”, or any wording implying a reviewer acts. AAASM-5657 | (none) |
| RC13 | Unmeasured | Whether a declared spend cap is checked in the decision path is not established by any manifest row | Same shape as RC6, and the same remedy. The manifest’s only budget row is the one for a store that is unreadable or corrupt, carrying coverage: unmeasured, failure_posture: fail_open_silent and evidence: gap — its gap reason records a positive control showing the budget path never queries the control-plane store. Risk scenarios’s T3 reaches Evaluated and states in the same table that it has no positive row; this register does not restate T3’s term over a row that does not carry it. What is bounded regardless: a cap exists only where a policy declares one, an undeclared budget is uncapped, reaching Denied before execution needs a caller that waits for the answer, and a corrupt store resets the cap to zero spend silently. See the two gaps | G9 |
| RC14 | Unsupported | Named transports and platforms are not available, and the matrix says which | Windows has no local mediation of any kind. UDP, QUIC and HTTP/3 are outside the transport set; so are HTTP/2, gRPC and WebSocket over an intercepted host, and MCP over WebSocket. Unsupported for one element is not Unsupported for the product | P4, N8, N11, M8 |
| RC15 | Denied before execution (via RC1) | Launching a tool through aasm run writes the proxy settings into the tool’s environment, which is what puts its outbound connections on the path | Writing a tool’s own settings file is tool governance, not a data-path claim; any prevention these adapters deliver is the proxy’s, borrowed through the launch environment. Of the shipped adapters, Claude Code is the only one above Integrated and the only one with a launch evidence test. Copilot’s launch always fails by construction. Codex and Windsurf inject the proxy variable with no CA trust, which is the configuration measured as failing the handshake silently. aasm run --no-proxy is an announced bypass. An unmanaged launch is a bypass and is not detectable | L1, L2, L3, L4, L5, L7, L8, H8, M10 |
| RC16 | Evaluated | An agent registers with an Ed25519 did:key identity and a possession proof, and delegation lineage is derived server-side | The agent plane is reachable without authentication by design, as a bootstrap path: an unauthenticated caller that can reach it can register and can submit policy queries. Those queries are evaluated with tenancy neutralised rather than with the caller’s own, so the exposure is that the plane accepts the call. Org scoping is applied per call site rather than at the storage layer. Do not describe the agent plane as authenticated | I1, I2, I3, I5, I6, I7 |
Two gaps this page found in the manifest
Writing the register surfaced two capability claims that no row among the 80 supports, and the finding is recorded here rather than absorbed into a hedge.
| Subject | What exists in the manifest | What is missing |
|---|---|---|
| The evidence pipeline — a decision durably reaching the audit chain | G10, “Audit emission failure”, domain: degraded_mode, coverage: unmeasured, evidence: gap | A capability row for the pipeline working. The manifest has no audit domain, and G10 measures only the failure case |
| Budget enforcement — a declared cap being checked in the decision path | G9, “Budget state unreadable or corrupt”, domain: degraded_mode, coverage: unmeasured, evidence: gap | A capability row for the cap being applied. There is no budget domain either, and Risk scenarios’s T3 independently records “no positive row” |
Measured across all nine domains — sdk, network, degraded_mode, mcp,
host_action, devtool_launch, identity, credentials, platform. Every G* row
is degraded_mode; none of the five rows carrying coverage: observed has the
evidence pipeline as its subject.
Why this is a finding and not a rewording. The claim vocabulary’s §4 says an
omitted evidence row is “a finding, and the remedy is to add the manifest row, not to
reword the sentence”. Adding rows is AAASM-5531’s, not this page’s, so what this page
can do is take the term its evidence actually carries and route the gap. Both entries
therefore read Unmeasured today and will move when the rows land.
What this does not license. Unmeasured here is a statement about the evidence, not
a claim that nothing is recorded — the emission code, the hash chain and
aasm audit verify-chain all exist and ship. Reading RC6 as “there is no audit log”
would be the understatement failure, which ADR 0034 treats as a defect in its own
right. The register says what is established; it does not say the opposite.
On the missing terms. Two of ADR 0033 §6’s eleven do not appear above.
Experimental is carried by one row, P1, and is folded into RC8’s bound rather than
given an entry of its own. Planned appears in no register entry deliberately: a
Planned item carries a ticket reference and no capability claim, so it is not a
thing a role page’s intervention section can be built on. Where a role brief needs to
point at future work it names the ticket in prose, which is what §6 requires.
How to read a brief
Each brief carries the same seven fields, in the same order, because a role page is judged on whether a reader can complete the sequence.
| Field | What it answers | Why the field exists |
|---|---|---|
| Pain | What is going wrong for this role today | The reader recognises themselves or leaves |
| Trigger | The event that makes it this role’s problem this quarter | Distinguishes a concern from a priority |
| Intervention | What Agent Assembly does about it — register entries only | The one field where a new claim could enter, and the register is why it cannot |
| Outcome | What is different afterwards, stated as a decision, not an averted consequence | Prevented-outcome wording is gated behind AAASM-5532 / AAASM-5529 |
| Proof | What the reader can check, and where | A role page that asserts without a route to evidence is a brochure |
| Limitations | What this role will discover later if the page does not say it now | The field that decides whether the page survives contact with an evaluator |
| Next | One next page and one next action | Two options is a decision; five is a menu |
Every brief’s Limitations field is long. That is not four different products having four different amounts of honesty — it is the same bounds, selected for what each role will actually hit. A limitations section thinner than its siblings is a signal that something was dropped, not that the role has fewer limits.
Brief 1 — Security / Risk
audience: security-engineer · Job: decide whether this changes the risk position
for agents already running, and what it does not cover.
Pain. An AI agent in your estate can already reach the network, the filesystem and a shell. The controls you own were built for humans and for services: identity, review, change management, and logs you read afterwards. None of them sits between the agent’s decision and the agent’s action. Your detection story is entirely retrospective, and your compensating control is that nobody has given the agents anything important yet — which stops being true the week a team ships an agent with a production credential.
Trigger. A team asks to run a coding agent against a repository that has deploy keys in it. You are asked to sign off, and the honest answer is that you have no mechanism to say what it may reach — only a mechanism to find out later.
Intervention. Agent Assembly is a decision point placed in front of an agent’s actions, on the paths you route through it, plus the record of what it decided. Concretely, for a security review: RC1 (routed egress refused before the dial), RC2 (address-space guard, on by default and not relaxable), RC4 (MCP tool calls checked by the control plane), RC3 (credentials removed from inspected requests), RC11 (configured-then-unreachable fails closed), and RC6 (what the decision record does and does not establish).
Outcome. For an agent you routed, a request to a destination outside the list you
configured is refused before a connection is opened. The security position that changes
is ordering — the decision precedes the effect — not coverage, and not the
completeness of the record: whether a given refusal’s entry durably reaches the audit
chain is RC6, which is Unmeasured. Buy the ordering; do not buy a ledger.
Proof.
- The bypasses are enumerated and published rather than argued away:
docs/src/devtools/limitations.mdand theknown_bypassesfield of every row in the capability manifest. - The threat model, the trust boundaries and the audit properties: Security model.
- Per-scenario decision, decider and boundary: Risk scenarios.
- What is on by default: the Level 3 table in Product promise.
- The claim discipline itself, including the wording this product refuses to publish: claim-vocabulary.md.
Limitations. In the order a reviewer will find them:
- Bypass resistance has exactly one rung and one row. ADR 0030 §4.1 reserves that
claim for
HostEnforced. RC9 is the whole of it: one path, macOS only, resting on a file read-back whose runtime honouring is unmeasured, and recorded as unearned at the published tag. Treat every other state — includingGatewayProtected— as saying nothing about bypass resistance. - The destination lists are empty by default (RC1). Out of the box this control refuses nothing. The always-on one is RC2, and it ranges over address space.
- Routing is per agent and per launch (RC15). An agent started outside the managed launch is outside the boundary, and that is not detectable.
- The largest gap is host actions. A shell command or subprocess spawned by a
native agent process has no interception mechanism at all in a released build
(
H1); browser automation (H6) and database queries (H7) likewise. The policy language can express these rules; nothing released can act on them. - Uninspected is not clean (RC7), and one path still records an allow where it should record nothing (AAASM-5637).
- The evidence is tamper-evident, not immutable, and it can be lost (RC6). A
dropped entry is indistinguishable from a deleted one, and no manifest row establishes
that a decision’s record durably arrives at all — the term is
Unmeasured. Do not present the audit chain as the control that satisfies a retention or non-repudiation requirement. - The agent plane accepts unauthenticated callers (RC16). This is a deliberate bootstrap path with a bounded exposure, and it is still not an authenticated plane.
Approval requiredis not a capability you can buy today (RC12).- Windows has no local mediation (RC14).
Next. Read Security model, then check your own platform and channel position in Compatibility before scoping a trial.
Brief 2 — Platform / SRE
audience: operator · Job: decide what this puts on my on-call rotation, and what
it does when it breaks.
Pain. Agent workloads arrive without an operational contract. They are started by developers on laptops and by CI on runners, they talk to third-party endpoints you did not approve, and when something goes wrong the first question — what did it actually do — has no owner and no answer. You are asked to make them safe without being given a place to stand.
Trigger. An incident review asks which agent made a call, and the answer takes two days of log correlation across three systems and is still a guess.
Intervention. A small number of processes you run and own: a control plane that answers policy questions and holds the record, a sidecar proxy on the wire, and a managed launch that puts a tool’s traffic in front of the proxy. What matters operationally is RC11 (configured-then-unreachable fails closed), RC10 (degradation is reported as a planned-versus-achieved pair), RC6 (what the decision record establishes, and what it does not), and RC15 (routing is a launch-time act you perform).
Outcome. Agent egress becomes a thing with a configuration, a failure posture and an owner, rather than ambient process behaviour. When the control plane is configured and goes away, the paths that depend on it refuse rather than quietly widening.
Proof.
- Deployment shapes and the container story: Docker and containers.
- What the running system exposes: Self-host observability.
- Version and platform position per component: Compatibility and Source of truth and status.
- Per-row failure posture and default state:
capability manifest
— the
failure_postureanddefault_statefields. - First-response material: Troubleshooting.
Limitations. Read these before you plan the rollout, not after:
- Fail-closed is not symmetric (RC11). Configured-then-unreachable refuses; a runtime with no gateway configured falls through to a local evaluation whose terminal default is allow. The difference is a configuration mistake away.
- Three failure modes are silent. An unreadable eBPF policy file falls back to an
empty rule set and raises no degradation event (
G7); a corrupt budget store resets the cap to zero spend (G9); a full audit channel drops the entry and the call still reports success (G10). None of the three pages you. - Degradation is emitted and rendered nowhere (RC10). The event type exists, the producers exist, and there is no consumer — the health endpoint’s degraded-layers field is a boot-time snapshot that never updates, and its status is a hardcoded literal. AAASM-5535. Plan to consume the event stream yourself, or plan not to know.
- The distribution position is not uniform, and it decides what you can install.
The proxy is a Linux release artifact; on macOS the only route is
cargo install aa-proxy. The eBPF loader daemon reaches crates.io only and is absent from the GitHub Release assets, the Homebrew tap and the install script — so an operator who installed through any of those has no host-level component and, on macOS, no proxy for the managed launch to start (RC8, RC9, AAASM-5653). - Kernel probes report; they do not decide (RC8), and the file probes are x86_64 only.
- The managed launch hands the child the entire parent environment (
C5), so a shell or file tool inside the agent can read any credential you exported. - The proxy refuses a non-loopback listener even with the remote-clients flag, because it has no listener TLS and no client authentication. Do not work around it.
llm_onlydefaults on. Broader inspection is a configuration you make, and it carries a latency and compatibility cost (RC3).- Windows has no local mediation (RC14).
Next. Read Self-host observability, then confirm your platform and channel in Compatibility.
Brief 3 — Engineering
audience: developer · Job: decide what adopting this costs me in my codebase, and
which of my actions it actually reaches.
Pain. You are shipping an agent, and the governance conversation arrives as a blocker rather than as a library. What you want to know is small and specific: what do I add, what does it wrap, what does it do when the policy says no, and what does it miss. What you usually get is an architecture diagram.
Trigger. Security asks for evidence of what your agent may do before it will be approved for a production credential, and there is nothing in the codebase to point at.
Intervention. Two integration shapes, and they are not equivalent. The SDK wraps your framework’s tool seam and checks a call before the tool body runs (RC5). The managed launch (RC15) puts the process’s outbound traffic in front of the proxy, which is where RC1, RC3 and RC4 apply. The first is in your code and is advisory. The second is out of your process and is where refusal actually holds.
Outcome. A wrapped tool call is checked before its body runs and, on the paths that fail closed, raises rather than executing. The decision is recorded against your agent’s identity, so the evidence question has an answer that is not a log grep.
Proof.
- Per-framework adapter status, per language, is the
framework_or_toolandcoveragefields of theSrows in the capability manifest. - Policy syntax and what it can express: Policy reference.
- SDK-level detail per language: the Python, Node and Go SDK docs, reached from Documentation.
- Where the SDK sits in the trust model, and why it is advisory: ADR 0033 §2 and §4, in ADR 0033.
Limitations. These are the ones that change an integration plan:
- The SDK is advisory by design (RC5). It is a defence-in-depth posture, not the authoritative gate. Refusal that holds against an uncooperative process is the proxy’s, out of your process.
- Node’s default mode produces no refusal. The check is routed through an allow-all
no-op client unless a check-capable mode is selected (
S7). Asking for enforcement without one is refused at init rather than silently allowed, and an auto-detected framework warns rather than throwing — deliberately, to preserve zero-config. This is the single most important sentence on any Node integration page. AAASM-4991. - Wrapping is not uniform across frameworks, and the difference is the deny signal. Some Python adapters raise before the body; others return a sentinel string, so a caller that catches only the policy exception treats a refused call as a success whose result is a string. The LangGraph and Mastra node hooks and the LangChain callback handler cannot refuse by construction — they observe. The explicit LangChain wrapper can, and it is off by default.
- Go requires an explicit call. The default build without the FFI tag and CGO denies every wrapped call rather than allowing them, which is fail-closed but is also not the advertised behaviour.
- A framework with no adapter is not covered (
S11), and a direct call that does not pass a patched seam is not covered (S10). - Anything the SDK does not wrap is outside it — raw HTTP, subprocess, filesystem,
a database driver, browser automation from inside your process (
S12). That class is the reason the proxy exists, and on host actions there is no released mechanism at all (H1,H6,H7). - MCP over stdio is not on the mediated path (RC4) — and it is the most common way tool servers are run.
- Routing has prerequisites in the environment, not in your source: the tool must be launched so the proxy variable is set and the CA is trusted. Codex and Windsurf inject the first without the second (RC15).
Approval requiredis not something you can integrate against (RC12).
Next. Read Policy reference, then pick your language’s SDK documentation from Documentation.
Brief 4 — Product / QA / Assurance
audience: auditor · Job: decide what can be tested, what can be signed off, and
what has to be written down as a known limit.
Pain. You are asked to give release confidence on a system whose behaviour is non-deterministic, whose failure mode is a side effect rather than a wrong answer, and whose test oracle — did the bad thing not happen — is an absence. Conventional assertions confirm that an error was raised, which is not the same fact.
Trigger. An agent-backed feature enters your release, and the acceptance criteria say “must not be able to” for the first time.
Intervention. The product turns “must not be able to” into a decision, on the paths you route. Four scenarios carry approved wording for reuse — the flagship egress refusal, secret exfiltration, a destructive production action and runaway cost — each with its decider, its default state and its boundary named. Behind them: RC1, RC3, RC4, and RC13. RC6 is here too, but as a bound rather than as a tool: it is what stops the audit log from being the thing you assert against.
Outcome. A refusal becomes an observable decision, so a test can assert on the
decision. Assert against the decision and against an independent observer, not
against the audit log — RC6 is Unmeasured, so a missing entry does not distinguish
“the decision was not made” from “the record was dropped”, and a test that reads the
log inherits that ambiguity as a flaky pass. Risk scenarios’s
negative control is built on an independent listener for exactly this reason.
Assertions about the averted consequence are a separate and stricter thing again —
see Limitations.
Proof.
- The four scenarios, each with a determination and its manifest rows: Risk scenarios — and specifically its negative control section, which specifies the absence check, the paired positive control that proves the check can see the effect, and the assertion ordering.
- The publication gate on prevented-outcome wording: the Tier 1 / Tier 2 split in the same page. Tier 2 is settled in wording and not publishable until AAASM-5532 and AAASM-5529 close.
- Per-row evidence: the
evidenceandevidence_runs_on_mainfields, present on all 80 rows, in the capability manifest. - What is deliberately not asserted yet: the Provisional table in Product promise.
- Release and version position: Compatibility.
Limitations. This is the field this role reads first:
- Approval is the gap, not a feature (RC12). No manifest row reaches
Approval required. The hold exists in the gateway path and fails closed on timeout, but no shipped operator surface can answer it, so in practice it holds and then refuses; inside the MCP tunnel a pending decision is downgraded to a refusal outright. An acceptance criterion written against a human approval step cannot pass today. AAASM-5657. - A prevented-outcome claim is gated. Designing a negative control is not the same as having run one. Until AAASM-5532 and AAASM-5529 close, describe the decision, not the averted consequence.
- An error is not an absence. An agent can receive a refusal and still have reached the endpoint by another route. A test that asserts on the error and not on the independent observer is measuring the wrong thing.
- Evidence quality is not uniform. Some rows are pinned by standing integration
tests; some by unit tests only; some carry no evidence at all and are recorded as
gaps. Two rows are explicitly marked
unit_only. Read the row before quoting it. - An empty audit log is evidence about the observer, not about the agent (RC6), and a passing chain verification does not mean the log is whole.
- Two of the sixteen register entries rest on no capability row at all. RC6 and
RC13 are
Unmeasuredbecause the manifest’s only rows for the evidence pipeline and for budget are the rows for those subsystems failing — see the two gaps. An acceptance criterion written against “the decision is in the audit log” or “the cap was applied” is currently asserting something the evidence base does not carry. AAASM-5531. - A budget cap exists only where a policy declares one (RC13), and a corrupt budget store resets it silently.
- Coverage figures are not available. No percentage, count of governed actions or fleet-level number may be derived — including from the four scenarios. Self-reported layer availability is not evidence of coverage.
- Three signals look like coverage and are not: an environment variable that
replaces the probe result outright, a proxy probe satisfied by a binary existing on
$PATH, and an SDK layer flag asserted unconditionally. Do not build a check on any of them. - Platform and channel change the answer, so a demo recorded on macOS is not evidence about a released Linux artifact (RC8, RC9, RC14).
Next. Read Risk scenarios and its negative-control section, then check the Provisional table in Product promise before writing an acceptance criterion.
Cross-brief consistency
The check the acceptance criteria turn on. Because every brief cites register entries rather than writing its own sentences, two briefs cannot state one capability at two strengths — a cell is either a citation of the register row or it is empty.
● cited · · not cited (silence, which is permitted) · ✗ explicitly stated as
absent.
| # | §6 term (single, from the register) | Security | Platform | Engineering | Product/QA |
|---|---|---|---|---|---|
| RC1 | Denied before execution | ● | · | ● | ● |
| RC2 | Denied before execution | ● | · | · | · |
| RC3 | Redacted | ● | ● | ● | ● |
| RC4 | Denied before execution | ● | · | ● | ● |
| RC5 | Denied before execution (Python, Go) · Evaluated (Node) · Unmeasured (Node default mode) | · | · | ● | · |
| RC6 | Unmeasured | ● | ● | · | ● |
| RC7 | Unmeasured | ● | · | · | · |
| RC8 | Observed · Detected | · | ● | · | ● |
| RC9 | — (ADR 0030 protection state) | ● | ● | · | ● |
| RC10 | Degraded | · | ● | · | · |
| RC11 | Denied before execution · Evaluated | ● | ● | · | · |
| RC12 | Approval required | ✗ | · | ✗ | ✗ |
| RC13 | Unmeasured | · | · | · | ● |
| RC14 | Unsupported | ● | ● | · | ● |
| RC15 | Denied before execution (via RC1) | ● | ● | ● | · |
| RC16 | Evaluated | ● | · | · | · |
Three properties of this table are the acceptance criteria, and each is checkable without reading the prose:
- Every row has exactly one term. A term is a property of the claim, not of the audience. Where a row carries several — RC5, RC11 — they are per path, and the register names which path takes which; they are not per audience.
- No brief carries a term for a row another brief contradicts. RC12 is the only row stated as absent, and all three briefs that mention it state it the same way; the fourth is silent.
- Every
·is silence, never a softer version. A brief that needed a weaker form of a register entry would have to add a register row, which is a change to all four briefs at once — which is the point.
What no role brief may say
- Not
catches everything,nowhere to hide,cannot be bypassedorunbypassable. The bypasses are enumerated in the manifest and published. These are ADR 0033 forbidden design 7 and are unwaivable — no time limit, named owner, approver or expiry makes an unsupported claim true (ADR 0034 Decision 10, as amended by AAASM-5671). - Not
every action,every tool call,full fleetorwhole fleet. Coverage is a per-agent, per-launch, per-platform fact. - Not
universal,comprehensiveorcompleteattached to coverage, protection, mediation, enforcement, visibility or audit. - Not
no code changes. The tool must be launched so its traffic reaches the proxy and the CA is trusted. State what is required. - Not
immutable audit. The chain is an unkeyed digest; retention pruning deletes rows. - Not
protects,enforces,catches,preventsorguaranteesused without a timing and a posture. If a sentence works with one of these, it is not specific enough to publish — pick a §6 term. - Not the fixed
SDK -> proxy -> eBPFpipeline as the architecture, in prose or as a three-box diagram, and not eBPF as a cross-platform final layer. Both are superseded by ADR 0033, forbidden designs 1 and 2. - Not
kernel-level enforcement. The kernel mechanism reports; the proxy refuses. - Not “the SDK denies the action before it runs” as a general statement. See RC5.
- Not “held for human review” on any scenario. See RC12.
- Not a coverage percentage, a count of governed actions, or a fleet-level number.
- Not a role-page headline carrying a claim without its bound on the same screen. A
<title>, anog:titleor a search snippet has no room for a boundary clause beside it, so those surfaces take Product promise’s headline, which is written to survive them.
Level 4 — claim-to-manifest mapping
Every register entry, resolved to the rows that evidence it, with the fields that
decide how strongly it may be stated. default_state and failure_posture are here
rather than in the register because they are the two fields a summariser drops first
and the two an evaluator checks first.
| # | Rows | Coverage term(s) in those rows | Decision timing | Default state | Failure posture | Reachability note |
|---|---|---|---|---|---|---|
| RC1 | N1 | denied_before_execution | pre | open (lists empty) | fail_open | shipped_with_platform_exception; macOS crates.io only |
| RC2 | N2 | denied_before_execution | pre | on | fail_closed | as N1 |
| RC3 | N3, C1, C4, C6, G4 | denied_before_execution (N3), redacted (C1, G4), detected (C6), unmeasured (C4) | in_line (all) | on (N3, C6) / open (C1, G4) / not_applicable (C4) | fail_closed (N3, C4) / fail_open (C1, C6, G4) | as N1; C6 ships everywhere |
| RC4 | M1, M3 | denied_before_execution | pre | off (M1) / on (M3) | fail_closed | as N1; M3 evidence is unit_only |
| RC5 | S1, S2, S5, S6, S7, S8, S9, S13, G5 | denied_before_execution (S1, S2, S5, S8), evaluated (S6, S9, S13, G5), unmeasured (S7) | pre (all) | on (S1, S2, S6) / off (S5, S8) / open (S7) / closed (S9) / mixed (G5) / not_applicable (S13) | fail_closed (S1, S2, S6, S8, S9, S13, G5) / fail_open_silent (S5, S7) | shipped; S13 has no non-test caller in-repo |
| RC6 | G10 | unmeasured | post | open | fail_open | shipped; AAASM-5626 |
| RC7 | N5, N10, N12, S10, S11, S12, L6, H1, H6, H7 | unmeasured | none | not_applicable / on (N5) | not_applicable | H1, H6, H7 are absent_mechanism |
| RC8 | H2, H3, H4, N13, I4, P1, P2 | detected (H2), observed (H4, P2), experimental (P1), unmeasured (H3, N13, I4) | post | off (H2, P1) / on (rest) | fail_open | shipped_crates_io_only — the loader daemon is unreleased |
| RC9 | L1; P3 | denied_before_execution (L1), unsupported (P3) | pre (L1) / in_line (P3) | on (both) | fail_closed (both) | L1 protection_state: host_enforced, macOS only, unearned at v0.0.1-rc.6; P3 demoted to integrated, tool_governance_only |
| RC10 | G6; G11; G7 | degraded (G6), unmeasured (G7, G11) | none (G6, G7) / post (G11) | open (G6, G7) / not_applicable (G11) | fail_open (G6) / fail_open_silent (G7, G11) | G6 is the only degraded row |
| RC11 | G1, G3, G8; G2 | denied_before_execution (G1, G3), evaluated (G8), unmeasured (G2) | pre | closed (G1, G3, G8) / open (G2) | fail_closed; G2 is fail_open | shipped |
| RC12 | (none) | — | — | — | — | No row reaches approval_required |
| RC13 | G9 | unmeasured | pre | open | fail_open_silent | shipped |
| RC14 | P4, N8, N11, M8 | unsupported | none | not_applicable | not_applicable | P4, M8 are absent_mechanism |
| RC15 | L1, L2, L3, L4, L5, L7, L8, H8, M10 | denied_before_execution (L1), observed (L5), unsupported (L4), unmeasured (L2, L3, L7, L8, H8, M10) | pre (L1, L2, L3, L7, H8, M10) / post (L5) / none (L4, L8) | on (L1, L2, L3) / off (L5, L7, L8, H8, M10) / not_applicable (L4) | fail_closed (L1) / fail_open_silent (L2, L3, L7, H8, M10) / not_applicable (L4, L5, L8) | shipped; L4 cannot launch by construction |
| RC16 | I1, I2, I3, I5, I6, I7 | evaluated (I1, I2, I3, I5, I7), unmeasured (I6) | pre (I1–I3, I5, I7) / none (I6) | on (I1–I3) / off (I5) / open (I7) / not_applicable (I6) | fail_closed (I1–I3) / fail_open (I5, I7) / not_applicable (I6) | shipped |
How to use this table when a brief changes. Re-resolve the row, not the sentence.
If a manifest row’s coverage, default_state or failure_posture moves, the register
entry moves, and every brief citing it moves with it — which is the property that keeps
four role pages describing one product.
What this page hands off
| Question | Owner |
|---|---|
| Implementing the four role surfaces, their routes, navigation and metadata | AAASM-5587 |
| The sitemap the role surfaces sit in | AAASM-5594 |
| The homepage, the Product page and the How It Works page | AAASM-5585, AAASM-5586 |
| The audience enum and the role crosswalk this page aligns to | AAASM-5591 |
Adding capability rows for the evidence pipeline and for budget enforcement, so RC6 and RC13 can move off Unmeasured | AAASM-5531 |
Reconciling Risk scenarios’s T3, which publishes budget at Evaluated over a row set the same table records as having no positive row, against RC13’s Unmeasured. The two pages disagree today; this one is the narrower and says so, but a disagreement between two Docs Hub pages is a defect to close, not a difference to keep | AAASM-5531 |
| Lifting the Tier 2 gate on prevented-outcome wording | AAASM-5532, AAASM-5529 |
Making Approval required claimable — a shipped operator surface for a held action | AAASM-5657 |
| Rendering a degradation event anywhere a user can see it | AAASM-5535 |
| Comprehension, accessibility and truthful-wording validation of the built pages | AAASM-5590 |
Last reviewed: 2026-08-07 — AI Agent Assembly Team
Last updated: 2026-08-07 by AI Agent Assembly Team
Product website and Docs Hub sitemaps
This page is for anyone about to move a page, add a route, or pick up one of the
navigation tickets. It draws the two trees — agent-assembly.com and
docs.agent-assembly.com — and says, for every page that exists today and every page
the model implies, which section it belongs to, which reader it serves, which job that
reader is finishing, and which ticket may touch it.
It exists because navigation is where a correct set of pages still fails. Every page
below can be accurate, correctly owned and correctly bounded, and a reader can still
land on a repository directory and give up. The current hub sections are named
Platform & Security, Getting Started, Operations, Reference, Support and
About — five of those six describe a kind of content, not a thing a reader is
trying to finish, and the one that names a task opens on two pages that are both
🗺️ Planned.
This page draws the trees; it does not build them. Every implementation ticket is named in the partition, and no page body is edited by this ticket except the two files that carry this page itself.
What governs this page
This page adds no product claim and coins no vocabulary. Everything it routes was decided somewhere else.
| Source | What it supplies |
|---|---|
audiences.md (AAASM-5591) | The six audience values, twenty jobs, forty information requirements, eight gaps, and the two checklists a proposed navigation is judged against |
role-narratives.md (AAASM-5584) | The four role briefs the website’s role surfaces are built from, and the shared claim register they cite |
page-standards.md (AAASM-5595) | The four disclosure levels, the page_type → required-levels table, the owner surface pairs, the area ids and the availability values |
| Content-layer ownership (AAASM-5592) | The L0–L6 roster, one canonical owner per content type, the narrowing rule and the four reuse patterns. content-ownership.md |
| Documentation inventory (AAASM-5593) | The census, the disposition vocabulary, and the finding that every current hub page except policy-reference.md is Keep, that one being Review. documentation-inventory.md |
| ADR 0033 §6 | The eleven claim terms, and forbidden designs 1 and 2. ADR 0033 |
| ADR 0034 §1 | The T1–T7 truth hierarchy, and hand-off 4 assigning the roadmap to L1. ADR 0034 |
source-of-truth.md | The status map, whose Maturity cell is the only place an area’s maturity is set |
Ticket references are plain text, not links: the tracker is not publicly readable, so a link would only reach a login wall — and a link checker scores that wall as reachable, which makes the reference look verified when it is not.
Why the product website’s tree is drawn on this hub
content-ownership.md puts positioning at L1 and reference material off it, so the
obvious reading is that the website’s sitemap belongs in the website’s repository. It
does not, for the same reason role-narratives.md — briefs for
four website pages — is a hub page: ADR 0034 puts the Docs Hub above the product
website in the truth hierarchy, so the website derives from the hub and never the
reverse. A sitemap held in two repositories is two sitemaps within one release.
The website repository carries a pointer at
design/content-briefs/AAASM-5594-sitemap.md, which restates no route table of its
own.
What this page does not decide
- Any product claim. Nothing here states what the product does. Where a route is named for a capability, the capability is the register entry’s, not this page’s.
- The wording of any page.
role-narratives.mdowns the role copy,product-promise.mdowns the promise. policy-reference.md’s fate. The hub and Core each publish an independent policy reference. That is the reference instance of prohibited duplication, anddocumentation-inventory.mdmarks the hub pageReview, owned by AAASM-5586 and AAASM-5609. This page files it under Reference because that is where a reader looks for it today; it takes no position on whether it survives.- Page metadata.
page-standards.mdowns the block. - Slugs on the product website. This page fixes the route count and the route prefix; AAASM-5587 picks the final slugs.
Two axes, and why the six sections are only one of them
The six sections the parent scope names — Evaluate, Integrate, Operate, Verify,
Reference, Contribute — are a task axis. audiences.md routes on a reader axis
of six audience values. Neither substitutes for the other, and collapsing them is the
mistake available here.
Filing by reader gives six role sections, and a reader with two jobs has to pick a
personality before they can pick a task. Filing by task alone leaves
audiences.md’s first navigation check unmet — “Six values, six routes. A value with
no entry means a reader with no way in” — which is gap GAP-1.
So the tree carries both, in different places:
- The sidebar is the task axis. Six sections, each named for something a reader is trying to finish.
- The index page is the reader axis.
README.mdgains a six-row router — one row peraudiencevalue, naming that reader’s entry, next step and escalation.GAP-1asks for exactly this and bounds it: the index already carries a role paragraph coveringoperatorandsecurity-engineerand routingdeveloperandcontributoroff-site, so the router extends that paragraph rather than replacing it, and the residue isevaluatorandauditor.
The Docs Hub sitemap
The six sections, and the task each one is
| Section | The task or decision a reader arrives with | audience values entering here | Jobs |
|---|---|---|---|
| Evaluate | Decide whether to adopt this, and learn what it leaves uncovered | evaluator, security-engineer | EV1, EV2, EV3, SE1 |
| Integrate | Add a policy checkpoint to an agent I am building | developer | DV1, DV2, DV3 |
| Operate | Run it on a host, and work out why a control did not fire | operator | OP1, OP2, OP3, OP4 |
| Verify | Check a published statement against the evidence behind it | auditor, security-engineer | AU1, AU2, AU3, SE2, SE3 |
| Reference | Look up one exact field, value or version | all six | supports every job; ends none |
| Contribute | Decide where a fact belongs, and write a page that conforms | contributor | CO1, CO2, CO3, CO4 |
Reference is the one section whose row is a lookup rather than a decision, and that is stated rather than dressed up. A reader in Reference is mid-task in one of the other five. It earns a top-level slot because the alternative — filing the policy reference under whichever task most often needs it — makes a lookup require knowing whose job it is, which is the failure this whole exercise is about.
Where every existing page goes
There are 24 pages under docs/src/ today, and this is a re-filing rather than a
migration: documentation-inventory.md’s census dispositions them all as Keep with
one exception — policy-reference.md is Review, owned elsewhere. Two of the 24 are
not in that census at all, because audiences.md and role-narratives.md merged after
it was taken; the census counts 22 and both of those are Keep by the same reasoning as
their neighbours.
Each page appears in exactly one section. The counts below sum to 24, which is the check that no page was filed twice or dropped — and it is a real count, not a reading of the tables: no page is assigned twice, none in the tree is unassigned, and none is assigned that is not in the tree.
Prefix chapters
Two, above the first separator, so they render above the six sections at every viewport width.
| Page | Purpose | Canonical owner |
|---|---|---|
README.md | The index, and the six-row audience router | L2:docs |
documentation.md | The component router — the standing route to /core/, /python-sdk/, /node-sdk/, /go-sdk/ and /arena/ | L2:docs |
Evaluate — 8 existing pages
| Page | Why it is here | Note |
|---|---|---|
product-promise.md | Levels 1–3 of the promise, including the default-posture table | Closes GAP-8 on the evaluator route only — one of the three routes that gap blocks. Opens the section until AAASM-5609 lands |
risk-scenarios.md | The flagship story and three supporting threats — IR-EV1-b | Home section; Verify links to its negative-control section rather than re-filing it |
security-model.md | The security-engineer entry — SE1 | Carries a superseded model (GAP-5); the rewrite is another ticket’s |
comparison.md | Category placement — EV1 | |
open-core-boundary.md | The open-source / commercial split — IR-EV3-c | Canonical owner of that split per the ownership table |
faq.md | First-visit questions, before any detailed page | |
quickstart-saas.md | Managed-service evaluation — EV3 | 🗺️ Planned; filed last, never in Operate |
cloud-deployment.md | Managed-service evaluation — EV3 | 🗺️ Planned; filed last, never in Operate |
Integrate — 0 existing pages, 1 new
Every page a developer needs at depth is L3, in the SDK docs. The hub’s job here is the choice, not the content. See the language route.
Operate — 3 existing pages, 1 new
| Page | Why it is here |
|---|---|
docker-containers.md | OP2, OP4 — what ships as an image and how the topology is wired |
self-host-observability.md | OP4 — what the running stack exposes |
troubleshooting.md | OP3 — why a control did not fire |
Verify — 1 existing page, 1 new
| Page | Why it is here |
|---|---|
saas-claim-publication-checklist.md | AU3 — the interim T3 register bounding managed-service claims |
Reference — 4 existing pages
| Page | Why it is here | Note |
|---|---|---|
policy-reference.md | Field-by-field lookup | Review — a second reference for content Core owns; AAASM-5586 / AAASM-5609 |
glossary.md | Term lookup | Carries the same superseded framing as security-model.md; AAASM-5658 |
compatibility.md | Version pairing across components | Generated from compatibility.toml |
source-of-truth.md | The status map — the only place an area’s maturity is set | Generated from hub-components.toml |
Contribute — 6 existing pages, plus this one
| Page | Why it is here |
|---|---|
page-standards.md | CO2 — the metadata contract |
audiences.md | CO1 — who a page is for |
role-narratives.md | The role copy contract |
sitemaps.md (this page) | Where a page goes |
docs-hub-aggregation.md | How the hub is assembled |
accessibility.md | Site policy |
localization.md | Site policy and the translation workflow |
Count: 2 + 8 + 0 + 3 + 1 + 4 + 6 = 24.
The pages this model implies but does not write
Three new hub pages, and two surfaces that close a gap but already have an owner. None of them is written by this ticket.
| New page | Section | What it is bounded to | Owner |
|---|---|---|---|
integrate.md | Integrate | A language chooser and a pointer to the SDK mode decision. It may not restate an install step, an API surface or a mechanism | AAASM-5608 |
operate.md | Operate | A router: route an agent → install for this platform → observe → diagnose. It may not restate a mechanism | AAASM-5608 |
verify.md | Verify | How to take a published sentence to its evidence, plus the vulnerability-reporting route (IR-SE3-a, which no hub page carries today) | AAASM-5608 |
| What Ships Today | Evaluate | The current capability and status answer | AAASM-5609 |
| Choose Your Enforcement Path | Evaluate | The path decision across runtime checkpoints, transport mediation and host adapters | AAASM-5609 |
There is deliberately no evaluate.md. AAASM-5609 already publishes the two pages
an Evaluate landing page would have been, and a third router above them would be a
derivative reproducing its source. Sequencing, so 5608 does not have to guess:
until 5609 lands, Evaluate opens on product-promise.md; when
5609 lands, What Ships Today becomes the first chapter and product-promise.md moves
below it. Both states satisfy audiences.md’s third navigation check, that no route
opens on a 🗺️ Planned page.
Choose Your Enforcement Path is filed in Evaluate, not Integrate or Operate, even
though DV2 and OP1 both need it — its own goal states it serves the evaluator
before integration begins. integrate.md and operate.md link to it. This is the
single decision that keeps those two new pages small enough to stay routers.
There is no reference.md or contribute.md. A section needs a landing page when
it is a route with a sequence; those two are indexes whose first entry is already the
entry point. This has a visible consequence — see the note on part titles in
navigation constraints.
The language route, and what mdBook cannot do
GAP-7 records that the SDK mounts “appear nowhere in the sidebar” and asks for
“a sidebar route to the mounts”. That cannot be done in SUMMARY.md, and the
measurement is below rather than the assertion.
An external URL in SUMMARY.md does not render as a sidebar link — mdBook resolves the
entry as a file path and the build aborts:
$ mdbook build # SUMMARY contains: - [Python SDK](https://docs.agent-assembly.com/python-sdk/)
ERROR Unable to create missing chapters
Caused by: failed to write `src/https://docs.agent-assembly.com/python-sdk/`
exit 101
The same book with that one line removed builds at exit 0, so the failure is the
external entry’s and not the fixture’s. The draft-chapter form - [Python SDK]()
builds, but renders <span>Python SDK</span> with no anchor — a label, not a route.
A second SUMMARY.md entry for a page that already has one is also rejected. This
is recorded because it was this page’s recommendation until review, and it fails the
same way the external link does:
$ mdbook build # SUMMARY has documentation.md as a prefix chapter AND under "# Integrate"
ERROR Summary parsing failed for file=".../src/SUMMARY.md"
Caused by: Duplicate file in SUMMARY.md: "documentation.md"
exit 101
Control: the identical insertion with a new unique file builds at exit 0, so the failure is the duplicate’s rather than the insertion’s. It would also have contradicted this page’s own one page, one section rule, the 24-page partition and the sidebar budget — a recommendation cannot be exempt from the rules the page hands the same ticket.
So SUMMARY.md offers exactly three shapes for a mount route, and each was built:
| Shape | Build | Result |
|---|---|---|
| External URL entry | exit 101 | failed to write src/https://… |
| Duplicate entry for a page already listed | exit 101 | Duplicate file in SUMMARY.md |
Draft entry - [Python SDK]() | exit 0 | Renders a <span> with no anchor — a label, not a route |
That leaves two real options, and neither is free:
- Move
documentation.mdout of the prefix into Integrate. Builds at exit 0, verified, and the page still renders. But it costs the prefix slot, and the reachability guarantee is stated over routes that do not pass through a task section — of which there are exactly two, both prefix chapters. Moving one leaves one, and the guarantee fails. Taking this option means restating that guarantee, not quietly weakening it. - A theme-level navigation block, outside
SUMMARY.md. The hub already injectstheme/head.hbsand fouradditional-jsfiles, so the mechanism exists. This is the only way to put the literal mount names in the sidebar, and therefore the only way to closeGAP-7’s sidebar half asaudiences.mdwords it. This is the recommendation, on the grounds that it is the one option that closes the gap and the one that costs no existing route.
If neither is taken, the honest position is that the sidebar half stays open and the mounts keep their one-hop prefix route. That is a smaller loss than it sounds — the route exists today and is unaffected by anything in this design.
The checkpoint half of GAP-7 stays open and stays L3 regardless, per that gap’s own
ruling and this repository’s project instructions: the hub orients toward component
docs and does not re-author their install steps or API surface.
Component documentation stays reachable
The parent scope requires it, and repository-shaped routes are the thing this redesign is removing, so the guarantee is stated as a count rather than a promise: each of the five mounts is reachable by exactly two routes that sit outside every task section, and by a third inside one.
| Route | Where it sits | Outside a task section? | Reaches |
|---|---|---|---|
documentation.md, a prefix chapter | Above the six sections, every page, every viewport | Yes | All five mounts |
README.md’s SDKs & components table | The index, also a prefix chapter | Yes | All five mounts, plus standalone per-version sites |
source-of-truth.md’s status map | Reference | No | All five, with owner, visibility and maturity |
The count is two, not three, and the third row is listed rather than counted — a reader
who has to enter Reference to find a component mount has been routed by task, which is
the thing this guarantee exists to rule out. Two is also the floor: taking option 1
in the language route moves
documentation.md inside Integrate and drops the count to one, which is why that
option is not the recommendation.
All three regions are generated from hub-components.toml, so a component added there
appears on all three without a navigation edit. That is the property that makes this a
guarantee rather than three lists to keep in step.
Managed-service content, and the gate that moves it
The parent scope forbids promoting unavailable Cloud functions as operational navigation. Three rules, each checkable:
- Placement.
quickstart-saas.mdandcloud-deployment.mdsit last in Evaluate. They are not in Operate, and they are not the first chapter of any section. Their reader today isEV3— deciding what to tell a stakeholder is not available yet — not an operator. - Label. A sidebar entry carries its area’s maturity label in the link text only
when that maturity is
🗺️ Planned. mdBook sidebar entries are plain links and cannot carry a badge, so the label has to be in the text or it is nowhere; and applying it to shipping pages too would train the eye to skip it. The current SUMMARY already does this, as “(Coming soon)” — the change is to use the status map’s own label instead of a second phrasing. - The promotion gate. A managed-service page moves from Evaluate into Operate
when, and only when, its area’s
Maturitycell insource-of-truth.md’sBEGIN GENERATED:hub-components:source-of-truth-tableregion stops reading🗺️ Planned. That is a string comparison against a generated table, not a judgement, and it means no ticket can promote a Cloud page by deciding to.
The product website sitemap
content-ownership.md gives L1 positioning, the evaluation narrative, trust,
early-access and conversion paths, and bars it from reference material, policy schemas,
threat models and API surfaces. Every route below is inside that boundary.
A structural fact this tree has to be designed around (documentation-inventory.md
finding D6): the website publishes two Markdown files, both blog posts, and sets
docs: false. Its copy is JSX inside .tsx components. So there is no Markdown
migration here, a page is a React route, and any check that enumerates .md files
passes over this layer without seeing it.
Routes
| Route | The task or decision | Status | Owner |
|---|---|---|---|
/ | Decide in five seconds whether this is relevant to me | Exists — rewrite | AAASM-5585 |
/product | Understand what it is before choosing a path | Exists — rewrite | AAASM-5586 |
/how-it-works | Understand the flow without source-level knowledge | New | AAASM-5586 |
/use-cases and four children | Recognise my own situation in a concrete story | New | Unassigned |
| Four role routes under one prefix | Decide relevance for my role in under three minutes | New | AAASM-5587 |
/trust | Get from a published claim to the evidence behind it | New | Unassigned |
/maturity | Learn what ships today, and what is decided but not built | New | Unassigned |
/blog, /blog/tags/* | Read build notes | Exists — keep | — |
/early-access | Register interest in the managed service | Exists — keep | — |
/arena | See governance trials | Exists — keep | — |
Every existing route is preserved, which is AAASM-5585’s own acceptance criterion.
The four role routes
role-narratives.md supplies four briefs and audiences.md’s crosswalk maps each to an
audience value. This page fixes the count (four, one per brief) and the
requirement that they share one route prefix — 5596 has to write one canonical-URL
rule, and it cannot if the four routes are scattered. The slugs are AAASM-5587’s.
| Brief | audience | Job it ends — this page’s derivation, not a cited mapping |
|---|---|---|
| Security / Risk | security-engineer | SE1 |
| Platform / SRE | operator | OP1–OP3 framing |
| Engineering | developer | DV2 |
| Product / QA / Assurance | auditor | AU3 |
The third column is derived here and is labelled so. The first two columns are
cited: role-narratives.md supplies the briefs and records each one’s audience value.
Neither source assigns these job ids to these routes — role-narratives.md assigns no
job ids at all, and audiences.md’s crosswalk assigns them to two rows, neither of them
these four. The mapping is this page reading each brief’s stated Job line against
audiences.md’s jobs table, which is a judgement a reviewer should be able to
disagree with rather than one they should take as sourced.
Each route carries the brief’s seven fields in the brief’s order. A fifth role route is
a change to role-narratives.md first, because a role surface with no brief is a page
authoring its own product truth.
Which audiences get an L1 entry, and which do not
audiences.md’s GAP-1 asks for a per-audience entry on both L1 and L2. L2 gets
all six. L1 gets five, and the sixth is a deliberate omission rather than a gap left
open, so it is accounted for here rather than in the gap table alone.
One of the five is contingent, and the number is worthless to a planner who does not
know which. evaluator’s L1 entry is / — and audiences.md says of the site as it
stands that it “publishes four pages and a blog and routes by none of them”. So /
is not an evaluator entry today; it becomes one when AAASM-5585 rewrites it around
problem, governed decision, outcome and proof. Until that lands, L1 has four, not
five. AAASM-5585 and AAASM-5587 both consume this count, and neither should read it as
already true.
audience | L1 entry | L2 entry |
|---|---|---|
evaluator | /, and /maturity for the forward-looking half | Evaluate |
security-engineer | Security / Risk role route | Evaluate — security-model.md |
operator | Platform / SRE role route | Operate |
developer | Engineering role route | Integrate |
auditor | Product / QA role route | Verify |
contributor | None, by design | Contribute |
contributor has no L1 entry because audiences.md’s own contributor section puts
positioning copy and conversion paths under belongs elsewhere for that reader. An L1
contributor entry would route them to the layer that page says is wrong for their job.
This is the one place the design does not deliver GAP-1’s page-shape as literally
worded, and it is stated rather than absorbed.
/maturity is where the roadmap goes, and it is not a hub page
GAP-6 records that no roadmap surface exists anywhere. ADR 0034 hand-off 4 assigns
the roadmap to L1, on the reasoning that a roadmap is a forward-looking positioning
statement and positioning is already L1’s. So the gap closes on the product website, not
on this hub, and a hub page named for a roadmap would be the wrong layer.
content-ownership.md bounds what may go on it: no dated commitment unless the date is
an already-released fix-version, and a forward-looking statement is admissible only in
one of three forms: ADR 0033 §6’s Planned term — a ticket reference carrying no
capability claim; ADR 0033’s Research label, which content-ownership.md marks
→ move because ADR 0033 uses the word once without defining it; or an area’s
🗺️ Planned maturity label.
/maturity uses the first and third of those three, and therefore carries two
things and no others: the current release position, narrowed from
source-of-truth.md, and a Planned list whose rows are ticket
references. It does not use Research, because that label is marked → move at its
source and a page built on a label in transit inherits the move.
What the website may not carry
Restated here because a sitemap is where these get violated, each by adding one reasonable-looking page.
- No reference material, policy schema, threat model or API surface. Those are L2
and L3.
/trustroutes to the evidence; it does not reproduce it. - No company or portfolio positioning. That is L0’s, on
horonomy.dev. - No architecture page built on a fixed pipeline of SDK, then proxy, then eBPF, in
prose or as a three-box diagram, and no depiction of eBPF as a cross-platform final
layer. ADR 0033 forbidden designs 1 and 2. This binds
/how-it-worksmost directly, which is why AAASM-5586 owns it rather than this page. - No claim without its bound on the same screen, and no scenario sentence in a
<title>, anog:titleor a social card — those takeproduct-promise.md’s headline, which is written to survive being quoted alone.
How current, limited, experimental and planned content stay separated
The parent scope asks for four states to be visibly separated. Three of the four are already values of one existing key and the fourth is a value of a different one, so this page coins nothing — coining a fifth vocabulary on the one axis a navigation routes by is the defect these artifacts exist to prevent.
| The state | Axis that already carries it | Value | Set in | Rendered as |
|---|---|---|---|---|
| Current | Page availability | available-verified | The page’s own metadata block | A page badge |
| Limited | Page availability | available-with-limits | The page’s own metadata block | A page badge |
| Experimental | Page availability | preview | The page’s own metadata block | A page badge |
| Deprecated | Page availability | deprecated | The page’s own metadata block | A page badge |
| Planned | Documentation-area maturity | 🗺️ Planned | source-of-truth.md, reached through the page’s area id | An area badge, and the sidebar link text |
Three consequences worth stating, because each is a way to get this wrong:
- Planned is not a page-availability value, and that is not an oversight. Maturity
belongs to a documentation area, and
page-standards.mddeliberately has nomaturitykey for exactly this reason. A page is🗺️ Plannedbecause its area is. ExperimentalandPlannedare also ADR 0033 §6 claim terms, about an action. A page badge and a claim term are different subjects, and ADR 0034 hand-off 7 rules that no axis may be applied to another’s subject. A page is notPlannedin §6’s sense; an action is.- The sidebar can only render one of the five. mdBook sidebar entries are plain
links, so the four availability values are page badges and only
🗺️ Plannedreaches the sidebar, in the link text. That asymmetry is the reason rule 2 of the managed-service gate is worded the way it is.
Page badges depend on pages carrying metadata blocks. Three of 24 hub pages do
today — audiences.md, page-standards.md and role-narratives.md, plus this one,
making four on merge. Adoption is AAASM-5610’s and the validator is AAASM-5601’s. Until
then the separation is carried by the sidebar label and the status map, both of which
work now.
Mapping the two existing plans in
AAASM-5013 — Golden Paths
5013’s target information architecture is three surfaces. Two of them are not hub content, and saying so is the point of mapping rather than absorbing.
| 5013 surface | Layer that owns it | Where it lands | Why |
|---|---|---|---|
| Developer Quickstart — Python | L3 python-sdk | /python-sdk/, routed from Integrate | Integration steps, per language is that SDK’s in the ownership table |
| Developer Quickstart — Node | L3 node-sdk | /node-sdk/, routed from Integrate | as above |
| Developer Quickstart — Go | L3 go-sdk | /go-sdk/, routed from Integrate | as above |
| Operator Quickstart | L3 Core | /core/, routed from Operate | Integration steps, operator / CLI path is Core’s |
| End-to-end governance walkthrough | L2, this hub | Operate | It spans gateway, runtime and SDK, so no single component owns it, and cross-component routing is L2’s job |
One finding 5013 should have before it decomposes. Its canonical scenario is
ALLOW read_file / DENY delete_file / APPROVAL send_email, held constant across all
three languages. The third leg is not runnable as documentation today:
role-narratives.md’s register entry RC12 records Approval required as no claim —
no manifest row reaches the term, no shipped operator surface can answer a held action,
and inside the MCP tunnel a pending decision is downgraded to a refusal. A golden path
that documents an approval step would be documenting a path a reader cannot finish.
AAASM-5657 owns the underlying gap.
AAASM-4237 — SaaS documentation IA
Most of 4237 is not this page’s to decide, so this records only the parts a sitemap settles and leaves the rest explicitly open.
Settled here:
- Where SaaS docs live — as L2 hub pages. Already decided by
content-ownership.md, whose L2 row names the managed-service pages as part of L2’s job, and bypage-standards.md, which states that a hub page about the managed service isL2:docswhileL3:cloudnames the private component only. Recorded so 4237 does not re-open it. - Where in navigation — last in Evaluate, under the gate.
- What they may say — bounded by
saas-claim-publication-checklist.md.
On the two ticket references, because 5608 will otherwise stop and check. AAASM-5608’s scope names SaaS documentation from AAASM-4224; this page maps AAASM-4237. That is not a contradiction and neither reference is wrong: 4224 is the SaaS-documentation Epic and 4237 is the information-architecture Task beneath it. This page consumes the Task, whose output is the IA; 5608’s scope names the Epic, whose output is the documentation set. Both stand at their own granularity.
Still 4237’s: the SaaS-specific audience sub-model, the screenshots and diagrams
policy, documentation review ownership, and how SaaS docs reference the private cloud
and E2E repositories without crossing the public content boundary.
Redirects
The Docs Hub restructure needs none, and this is measured
An mdBook page’s URL is derived from its file path, not from its position in
SUMMARY.md. So re-parting the sidebar moves no URL. That is the claim, and it was
tested rather than assumed:
| Step | Result |
|---|---|
Build the hub unchanged, list every emitted .html | 27 files |
Move product-promise.md and risk-scenarios.md into a new # Evaluate part; rebuild | exit 0 |
diff the two file lists | identical — 0 URL changes |
Control: does the sidebar actually change? Evaluate in the generated toc-*.js | 0 in base, 1 in mutant |
Control: is the probe capable of finding a part title at all? Reference, an existing part | 1 in base |
| Second control: prev/next chapter links | changed, troubleshooting.html → faq.html |
The mutation moves the token under test and the URL set does not move with it, so the result is a property of mdBook rather than of a probe that saw nothing.
Consequence for AAASM-5608: the re-filing above creates zero redirect
obligations. Every external link into the hub keeps resolving — including the five
docs.agent-assembly.com/*.html deep links in the website’s mega menu and a sixth
in src/components/home/NextSteps.tsx. Re-derived with
git grep -nE '\$\{DOCS(_URL)?\}/[A-Za-z0-9_-]+\.html' origin/main -- src, because counting
by eye had missed quickstart-saas.html twice over — the one page this design actually
relocates, and so the single most relevant row.
This is also why the three new pages are flat files at docs/src/*.md rather than a
directory per section: a directory would change nothing today but would set the
precedent that a section rename is a URL change.
Keep it that way. docs/book.toml has no [output.html.redirect] section, so a
page that is genuinely renamed later would 404 with nothing to catch it. Adding that
section is the prerequisite for any future rename, and it is not needed for this design.
What this design does create
| Obligation | Where | Owner |
|---|---|---|
If /how-it-works takes the homepage’s architecture section, / must keep an #architecture anchor | official-website | AAASM-5586 |
That one is worth spelling out because the usual remedy does not apply: a URL fragment
is never sent to the server, so no 301 can redirect #architecture. Either the anchor
stays on / or the link breaks silently. The same applies to #security.
Nothing else moves. Every other route on both sites is either unchanged or new.
Redirects owned elsewhere, and not by this design
Recorded so a reader does not conclude from the section above that redirects are handled. They are not, and three separate gaps are open:
-
Five legacy
ai-agent-assembly.github.io/<repo>/URLs have canonical targets ondocs.agent-assembly.com, and none of the five is implemented. Owned by AAASM-3665. The five are enumerated below with their measured status, because an earlier draft gave the total without its parts and got the live-content count wrong — it said one of them serves live content, and four do.Legacy URL Measured 2026-08-08 Serves content? …github.io/agent-assembly/200, 2,896 bytes Yes — landing page with a meta-refresh …github.io/python-sdk/200, 1,423 bytes Yes — meta-refresh …github.io/node-sdk/200, 27,147 bytes Yes — a full live page …github.io/go-sdk/200, 3,329 bytes Yes — meta-refresh The fifth row — the pre-rename docs host, spelled out in MIGRATION.md404 No — that repository was renamed under AAASM-4341 The fifth row’s literal host is not written here on purpose:
check_repo_names.pyaudits every tracked page for retired repo names andMIGRATION.mdis its one content exemption, as the deliberate history record. Naming the URL here would either fail that gate or require widening its allowlist to cover a whole page, which is suppressing a scanner rather than satisfying it.So “none is implemented” holds — no row 301s to its canonical target — but four of the five serve 200, not one. The fifth carries a redirect obligation for a URL that no longer resolves at all, which makes that row of the plan moot rather than pending and AAASM-3665’s remaining work four rows rather than five.
documentation-inventory.mdnames onlyagent-assembly/as an example of live content; reading that as the count was this page’s narrowing, not the inventory’s claim. The stale fifth row is filed as AAASM-5690 against the inventory andMIGRATION.md.Not in the five, and worth knowing before someone re-derives this list:
…github.io/docs/301s todocs.agent-assembly.comalready. It is not one of AAASM-3665’s five rows, so it neither contradicts nor satisfies them. -
The host-level
wwwredirect is proposed, not applied. -
Core’s own book has no
[output.html.redirect]either, which its migration slice needs before its three publishedMove/Mergepages land.
One page, one section
The rule that keeps the trees free of duplicate architecture and reference pages:
A page appears in exactly one section. Every other section that needs it links to it.
The 24-page assignment above satisfies this by construction — the section counts sum to 24, so no page has two homes. Four pages are wanted by more than one section, and each resolves the same way:
| Page | Home | Also linked from |
|---|---|---|
risk-scenarios.md | Evaluate | Verify, to its negative-control section |
source-of-truth.md | Reference | Every section, for its area’s maturity |
compatibility.md | Reference | Evaluate, Operate, Verify |
open-core-boundary.md | Evaluate | Operate, for the self-host scope |
Two stronger constraints, because linking is not enough on its own:
- This design creates no architecture page and no reference page for anything Core
owns. Architecture is Core’s — ADR 0033 and
docs/src/architecture/— and the hub’s L2 row bars it from “a reference of its own for anything Core owns”. The three new hub pages are routers, and the bound in their table is what keeps them that way: a router that starts explaining a mechanism has become a fourth copy of it. - The existing duplicate is not made load-bearing. The hub and Core each publish an
independent policy reference, and
README.mdframed the product with a three-layer model ADR 0033 supersedes. Neither was this ticket’s to fix — they are AAASM-5586 / AAASM-5609 and AAASM-5611 respectively — but AAASM-5608 must not build the Evaluate route on top of thatREADME.mdsection, because a navigation that routes readers into superseded framing makes the framing harder to remove. AAASM-5727 has since replaced that section’s framing; the constraint stands, because the route must be built on the corrected wording, not the superseded one.
Navigation constraints, desktop and mobile
Docs Hub — mdBook
| Constraint | Value | Why |
|---|---|---|
| Sidebar entries, total | 36 after this design — 24 existing + this page + 3 new routers + 2 from AAASM-5609 = 30 page entries, plus 6 part titles | Below a viewport height at 1080px, the sidebar scrolls; on mobile the whole tree renders in one drawer |
| Nesting depth under a part | 2 levels | A third level is a drawer nobody scrolls to |
| Section headings | Not links | part-title renders as <li class="part-title">Evaluate</li> — plain text, no anchor. A section that needs a landing page must have one as its first chapter, which is why Evaluate, Integrate, Operate and Verify have one and Reference and Contribute do not |
| Sidebar entry decoration | Link text only | No badge mechanism exists, hence the label rule for 🗺️ Planned |
| Breadcrumbs | No mechanism | mdBook renders no breadcrumb trail. AAASM-5608’s scope asks for contextual breadcrumbs; in mdBook that is a theme change or it does not happen. Recorded so 5608 does not plan against it |
| Per-entry layer / component / maturity labels | No channel beyond link text | AAASM-5608’s scope asks navigation labels to identify content layer, component and maturity. The sidebar has one string per entry and no data attributes, so three facts cannot ride on it. Either they go in the link text — which is how 🗺️ Planned reaches the sidebar — or they belong on the page, not in the nav |
External links in SUMMARY.md | Not supported | Build aborts at exit 101; see the language route |
Product website — Docusaurus
| Constraint | Value | Why |
|---|---|---|
| Navbar items | Unchanged at 3 left, 3 right (six total) | AAASM-5587 requires cross-links without an oversized mega menu, and the drawer at narrow widths is a vertical list of the same items |
| New routes reach readers via | The Product navbar item becoming a three-entry dropdown, a role chooser block on /, and the footer | A dropdown of three is not a mega menu; the two existing mega menus stay as they are |
| Footer columns | 3, unchanged; /trust and /maturity join Resources | A fourth column wraps below 768px |
| Locale | Every new route exists in en and zh-Hant | The site declares both; a route that exists in one serves English content on a translated URL, which D6 already records as happening for the blog |
The role routes deliberately do not become navbar items. Four more top-level items would put the drawer at ten, and a role page is something a reader is routed to from a chooser, not something they navigate to by name.
Partitioning this into non-overlapping tickets
Each row names the files it owns. No file region appears in two rows — the distinction is load-bearing, and the earlier wording (“no file appears in two rows”) was simply false. Two overlaps exist: one is a genuinely shared file that predates this page, the other is disjoint by region within a file. Both are named below rather than counted as clean.
| Ticket | Repository and paths | Routes or sections | Must not touch |
|---|---|---|---|
| AAASM-5608 | docs: docs/src/SUMMARY.md; new docs/src/{integrate,operate,verify}.md | The six sections | Any existing page body |
| AAASM-5611 | docs: bodies of README.md, security-model.md, comparison.md | — | SUMMARY.md |
| AAASM-5609 | docs: two new evaluator guides; policy-reference.md | First two chapters of Evaluate | SUMMARY.md ordering beyond its own two entries |
| AAASM-5586 | official-website: src/pages/product.tsx, new how-it-works route; docs: policy-reference.md | /product, /how-it-works | /, the role routes |
| AAASM-5585 | official-website: src/pages/index.tsx, src/components/home/** | / | Navbar, footer, /product |
| AAASM-5587 | official-website: new role route files | Four role routes | /, /product, navbar |
| AAASM-5596 | official-website: docusaurus.config.ts, src/components/MegaMenu/menus.ts, _headers; docs: _headers, docs/book.toml | Navbar, footer, canonical links, redirects | Any page body |
| AAASM-5658 | docs: glossary.md | — | SUMMARY.md |
| AAASM-5610 | docs: metadata blocks on existing pages | — | Page prose |
| Unassigned — use cases | official-website: new /use-cases routes | /use-cases and children | — |
| Unassigned — trust | official-website: new /trust route | /trust | — |
| Unassigned — maturity | official-website: new /maturity route | /maturity | — |
| Unassigned — walkthrough | docs: end-to-end governance walkthrough | Operate | — |
The two overlaps, both named rather than left to be discovered.
policy-reference.mdis named by both AAASM-5586 and AAASM-5609, becausedocumentation-inventory.mdassigns it to both. That pairing predates this page and this page does not split it. This one is a genuine shared file, not a shared region.- AAASM-5610 touches every page that any other row touches. Its slice is metadata
blocks on existing pages — all 24 — which intersects AAASM-5611 (
README.md,security-model.md,comparison.md), AAASM-5658 (glossary.md) and AAASM-5609 (policy-reference.md). The rows stay disjoint because 5610 owns theAA-PAGE-METAblock and the others own the prose below it, and the block is the first construct in the file with a fixed delimiter pair, so the two regions cannot be confused. That is a disjointness by region, and the acceptance criterion is worded per page — so it is recorded here rather than counted as clean. If 5610 lands concurrently with any of the three, expect a same-file merge, not a conflict.
Ordering. AAASM-5608 can land before 5609, 5611 and the unassigned rows, because
SUMMARY.md and the three new routers do not depend on any of them. It should land
before AAASM-5596, so canonical links and navigation are written against the final tree.
Which gaps this closes, and which it carries forward
audiences.md requires that every gap is “either closed by a page in the proposal, or
carried forward as a named open item”. All eight, and four of them are carried forward
rather than closed.
| Gap | Disposition |
|---|---|
GAP-1 — no surface routes all six audiences | Closed at L2; five of six at L1. audiences.md asks for a per-audience entry on both L1 and L2. L2 gets six: the index router plus the six sections. L1 gets five — four role routes, plus / for evaluator, and that fifth is contingent on AAASM-5585 rewriting /, since audiences.md records that the site routes by none of its current pages. Until 5585 lands the L1 count is four. contributor gets none, deliberately: audiences.md’s own contributor section puts positioning and conversion paths under belongs elsewhere, so an L1 contributor entry would route that reader to the wrong layer. Stated as five rather than six so 5608 and 5587 do not both assume the other built it |
GAP-2 — Getting Started holds only 🗺️ Planned pages | Closed. The section is dissolved; operate.md is the operator entry and starts from what ships; the two Planned pages move to the end of Evaluate |
GAP-3 — no channel-and-platform position published | Carried forward; no slot defined here, and that is the disposition. An earlier draft said a Reference slot is defined — none is: Reference lists four existing pages and the implied-pages table has no Reference row. The content belongs to AAASM-5609’s What Ships Today, which this design files in Evaluate, so 5609 owns it and 5608 must not build a second matrix anywhere. Blocked on AAASM-5680 for the GHCR vocabulary |
GAP-4 — the evidence layer has no reader-facing surface | Carried forward. A Verify slot is defined and backed by verify.md. The manifest that feeds it is AAASM-5531, which is Done, so the live owner is AAASM-5600 (To Do) — ADR 0034’s T3 row names the pair, and pointing a carried-forward gap at a closed ticket is how it stops being tracked. ADR 0034’s T3 registry still does not exist |
GAP-5 — the security entry carries a superseded model | Carried forward. security-model.md keeps the security-engineer entry slot in Evaluate; the rewrite is AAASM-5611’s |
GAP-6 — no roadmap surface | Carried forward, and relocated. It closes at L1 as /maturity, not on this hub. Unassigned |
GAP-7 — SDK mounts absent from the sidebar | Not closed by this design, and narrower than stated. All three SUMMARY.md shapes were built and none delivers a sidebar route: external entry and duplicate entry both exit 101, the draft form renders no anchor. Only a theme-level block closes the sidebar half as worded, and that is a 5608 decision this page recommends rather than makes. The mounts keep their existing one-hop prefix route meanwhile. The checkpoint half stays L3 |
GAP-8 — the default-posture table is filed under About | Closed on one route of three. The page-shape is a level-3 surface on those routes, and only the evaluator route gets one: product-promise.md moves to Evaluate, satisfying IR-EV1-a and IR-EV1-c. IR-OP3-a is not closed — the table is one link from the operator route, and one link from is not on. IR-DV2-a is not closed either: audiences.md marks it not on a developer route, and this design bars integrate.md from restating a mechanism. Closing the other two is an L3 SDK ticket or an AAASM-5609 link, not a page 5608 can draw. An earlier draft counted the operator route as closed; it is not |
The one navigation check this design does not pass
audiences.md’s fifth check is that “no route requires a reader to reach L6 to finish
their job”. It is not met, and no sitemap can meet it alone. The auditor’s AU1 — find
the evidence behind a published claim — still terminates at capability-manifest.yaml
and verification-reports/**, both L6, and content-ownership.md states that nothing
in L6 is a reader-facing page. That is GAP-4, and the fix is a published surface, not
a route. This design defines the slot the surface will occupy and leaves the check
failing until something fills it.
The other four checks are met: every audience has an entry, every route reaches its
escalation in three steps, no route opens on a 🗺️ Planned page, and every gap above is
closed or named.
Entry, next and escalation under this tree
audiences.md’s second check, applied once.
audience | Entry | Next | Escalation |
|---|---|---|---|
evaluator | Evaluate — What Ships Today, or product-promise.md until it lands | risk-scenarios.md, open-core-boundary.md | ADR 0033 §5.3 and §6, in Core |
security-engineer | Evaluate — security-model.md | risk-scenarios.md, then Verify | Core’s threat model, the manifest, verification reports |
auditor | Verify — verify.md | saas-claim-publication-checklist.md, risk-scenarios.md’s negative control | The manifest and verification reports (L6 — GAP-4) |
operator | Operate — operate.md | docker-containers.md, self-host-observability.md | Core’s quick start and CLI reference |
developer | Integrate — integrate.md | The SDK mount for their language | Core’s API reference, the examples repository |
contributor | Contribute — page-standards.md | audiences.md, this page | ADR 0034, content-ownership.md, claim-vocabulary.md |
How this page meets its acceptance criteria
| Criterion | How it is met |
|---|---|
| Each top-level item corresponds to a user task or decision | The six sections each name a task and the jobs it ends, and the product-site table gives a task per route. Reference is stated as a lookup rather than a decision rather than being described as one |
| The sitemaps avoid duplicate architecture and reference pages | One page, one section. The 24 assignments sum to 24, so no page has two homes; the four cross-wanted pages are resolved by link; and the design creates no architecture page and no reference page for content Core owns. The two duplicates that already exist are named with their owning tickets rather than absorbed |
| Current, limited, experimental and planned content are visibly separated | The label crosswalk. Three states are availability values, one is an area maturity label, and the asymmetry between them — only one reaches the sidebar — is stated with the mechanism that forces it |
| Mobile and desktop navigation constraints are considered | Navigation constraints, with a numeric budget per surface, each re-derived from the config or the build rather than recalled. Four constraints are measured rather than assumed: part titles are not links, SUMMARY.md rejects an external entry, it rejects a duplicate entry, and the navbar is six items. Two are recorded as absent mechanisms so 5608 does not plan against them: breadcrumbs and per-entry labels |
| The implementation can be divided into non-overlapping page or route tickets | The partition. Thirteen rows naming their own files, a landing order, and two overlaps named rather than counted as clean — policy-reference.md shared by AAASM-5586 and AAASM-5609, which predates this page, and AAASM-5610, which is disjoint from three other rows by region within a file rather than by file. The criterion is worded per page, so the second is a qualified pass and is labelled one |
What this page hands off
| To | What |
|---|---|
| AAASM-5608 | The tree, the 24-page assignment, the three new routers and their bounds, the sequencing against 5609, the zero-redirect measurement, and the two constraints mdBook imposes |
| AAASM-5611 | That README.md’s three-layer section is on the Evaluate route and should be corrected before that route is built on it |
| AAASM-5585 · AAASM-5586 · AAASM-5587 | The route table, the four-role count and the shared-prefix requirement, the #architecture anchor obligation, and the navbar budget |
| AAASM-5596 | The final tree to write canonical links against, the fact that the hub restructure creates no redirect obligation, and the three redirect gaps that are open elsewhere |
| AAASM-5609 | That GAP-3 is routed to What Ships Today rather than to a second matrix, and that Choose Your Enforcement Path is filed in Evaluate and linked from Integrate and Operate |
| AAASM-5013 | The layer split across its three surfaces, and that its canonical scenario’s approval leg rests on a term no manifest row reaches |
| AAASM-4237 | The three questions a sitemap settles, and the four that remain its own |
| AAASM-5601 · AAASM-5610 | That the sidebar label and the status map carry the maturity separation until page badges exist |
Last reviewed: 2026-08-08 — AI Agent Assembly Team
Last updated: 2026-08-13 by AI Agent Assembly Team
How this documentation hub is assembled
This site is a central hub that aggregates the documentation of every AI Agent Assembly module into one place, under stable subpaths, with one unified search. Each module keeps its own documentation toolchain — the hub pulls and assembles them rather than forcing a single generator.
| Path | Module | Generator |
|---|---|---|
/ | This hub | mdBook |
/core/ | agent-assembly (core monorepo) | mdBook |
/python-sdk/ | python-sdk | mkdocs-material |
/node-sdk/ | node-sdk | Docusaurus |
/go-sdk/ | go-sdk | Hugo + Hextra |
/arena/ | arena | mkdocs-material |
What gets aggregated
For the hub’s canonical view, each module’s default channel (its latest
line, built from master/main HEAD) is mounted at /<module>/. The per-module
standalone sites keep their full per-version channel browsing — the
component table on the home page links out to them.
Unified search
A single Pagefind index is built over the final assembled site, so the search box on this hub finds pages across the hub and every module in one query — even though each module was built by a different generator.
The contract
The machine-readable module registry, the build/copy contract, the per-generator
base-URL strategy, and the versioning decision are documented in
AGGREGATION.md
at the repository root, and implemented by docs/scripts/aggregate.sh +
.github/workflows/aggregate.yml.
Last updated: 2026-07-11 by AI Agent Assembly Team
Accessibility statement
This page states the accessibility posture of the AI Agent Assembly documentation hub for readers — and for procurement or compliance reviewers — who need a public statement of intent before adopting the product.
Conformance target
We are working toward WCAG 2.1 Level AA for this documentation site. This is a target we are actively pursuing, not a certified conformance claim. As gaps are found they are tracked and fixed rather than waived.
What we do today
- Semantic, keyboard-navigable content. The site is built with mdBook, which renders plain semantic HTML with a keyboard-operable sidebar, search, and theme controls.
- Readable contrast in light and dark. The default light theme and the dark themes aim to meet the WCAG AA contrast ratio for body text.
- Text alternatives. Informative images carry alternative text, and architecture diagrams are accompanied by a prose or tabular description so the same information is available without seeing the diagram.
- Resizable, reflowable text. Content reflows without loss of information when zoomed or viewed on a narrow screen.
Known limitations
- Some Mermaid diagrams are rendered as SVG; where a diagram is essential we provide an adjacent text description, but not every diagram has full alternative markup yet.
- The site depends on the upstream mdBook theme; a small number of its controls may not yet fully meet AA, and we track those upstream.
Feedback
If you hit an accessibility barrier on this site, please open an issue on the docs repository. Tell us the page, what you were trying to do, and the assistive technology or browser you were using — we treat accessibility barriers as bugs.
Last updated: 2026-07-15 by AI Agent Assembly Team
Localization
This documentation is authored in English, and Traditional Chinese
(繁體中文, zh-Hant) is available as a first-pass translation of the priority
pages. This page states that policy for readers evaluating the product
worldwide and records how translations are produced and contributed. Additional
languages are welcome — see the contributor workflow below.
Current status
- The English source (
en) is the single source of truth for every page. - 繁體中文 (
zh-Hant) ships a draft, machine first-pass translation of the priority pages — the landing/introduction, the FAQ, the Glossary, and the Quick start (SaaS). Those pages carry a banner noting they are pending native review. Every other page falls back to English until it is translated. - A language switcher in the page header (English ↔ 繁體中文) lets readers move between the two, page for page.
How localization works
This hub uses mdBook’s gettext-based localization via the
mdbook-i18n-helpers
toolchain — the standard approach for multilingual mdBook sites. The English
source stays authoritative; each translation is layered on top as a PO catalog,
so English content cannot drift silently from its translations.
-
Extract the English source into a
po/messages.pottemplate:cd docs MDBOOK_OUTPUT__xgettext__pot_file=messages.pot mdbook build -d po -
Translate per-language
po/<lang>.pocatalogs (for examplepo/zh-Hant.po). Untranslated strings are left empty and fall back to the English source at build time. -
Build one localized site per language by overriding the book language, which activates the
gettextpreprocessor (configured inbook.toml):MDBOOK_BOOK__LANGUAGE=zh-Hant mdbook build -d book/zh-HantThe aggregation pipeline (
docs/scripts/aggregate.sh) builds English at the site root and each translated language under/<lang>/(e.g./zh-Hant/), and the theme’s language switcher links between them.
Toolchain note. The default English build treats the
gettextpreprocessor as a no-op (there is nopo/en.po), so contributors who only touch English content still just runmdbook build. Producing or previewing a translated build additionally requires themdbook-gettextbinary (cargo install mdbook-i18n-helpers).
Contributing a translation
Translations are community-contributed. To improve the zh-Hant draft or add a new language:
-
Re-sync the template if you changed English content (step 1 above), then merge it into the catalog you are editing:
msgmerge --update po/zh-Hant.po po/messages.pot # or msginit for a new language -
Fill in the
msgstrentries inpo/<lang>.po. Use the Glossary to keep technical-term choices consistent, and keep product, crate, and API names (e.g.aa-gateway,aa-proxy,aa-ebpf, SDK and CLI identifiers) in English. -
Validate with
msgfmt -c po/<lang>.poand preview the localized build with the command in step 3 above before opening a PR.
Emphasis in a CJK language: write <em>, not * or _
In a language written without spaces, a one-character emphasis marker does not survive to the page. Use inline HTML instead:
因此其宣告層級為<em>已觀測</em>與<em>已偵測</em> ← renders
因此其宣告層級為*已觀測*與*已偵測* ← renders as literal underscores
Two things stack up here, and the second is why the obvious fix does not work:
- CommonMark’s flanking rules treat
_between two CJK characters as intraword, so_文字_is not emphasis at all and the underscores are printed. - The gettext preprocessor re-serialises every translated string through a
Markdown writer, and that writer emits emphasis as
_…_whatever you wrote. So*文字*is converted to_文字_before the page is rendered, and lands in exactly the same place.
Measured on this catalog: of the 13 broken spans, five had already been
switched from _ to * by someone applying the obvious fix. All five still
rendered with literal underscores. Verifying against the catalog would have
shown them as fixed — check the built HTML, which is the only place the
difference is visible (AAASM-5742).
**strong** also survives, because the writer emits ** for strong. Prefer
<em> where the English source uses emphasis, so the translation carries the
same weight rather than being upgraded to bold. This matters most on the
ADR 0033 §6 claim terms,
where the emphasis marks controlled vocabulary rather than decorating it.
If you would like to coordinate before starting, please open an issue on the docs repository naming the language you want to work on.
Marketing site. The marketing site (
agent-assembly.com) is built with Docusaurus, which has its own built-in i18n. Enabling localization there is tracked separately from this docs hub; this workflow covers the mdBook documentation hub only.
Last updated: 2026-08-14 by AI Agent Assembly Team