Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AI Agent Assembly Documentation

core python-sdk node-sdk go-sdk license

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 modelSecurity model
Compare AI Agent Assembly to other toolsWhy AI Agent Assembly?
Know what is open source vs. paidOpen 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 policyPolicy 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.

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:

  1. 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.
  2. 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 honour HTTP_PROXY/HTTPS_PROXY and trust the CA (on macOS the install is attempted at proxy start via security add-trusted-cert, which requires admin authorization — macOS prompts, and a refusal fails proxy startup; on Linux run sudo 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.
  3. 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.

Next step Run a governed example →

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.
Prefer learning by running code? Open the examples repo →

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.

SourceWhat it decides for this page
The capability and evidence manifest (AAASM-5531), in the agent-assembly repositoryEvery row in every table below. Nothing inside a generated block is hand-written
ADR 0033 §6The 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 promiseThe approved wording, the default-posture table, and which term each mechanism reaches. This page does not restate any of the three
Source of truth & statusWhich 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.yaml in the ai-agent-assembly/agent-assembly repository — the capability and evidence manifest defined by AAASM-5531 — through the pinned extract capability-surface.toml in this repository.

Manifest version 1.0.0 · 80 capability rows · taken at commit e2730ddaf · the manifest’s own evidence tree 299de3883, 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 candidate area 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 termRowsAreasCapability ids
Observed5Developer-tool launch, Host actions, Platform host-level interception, SDK and framework seamsS3 S4 H4 L5 P2
Detected2Credentials, Host actionsH2 C6
Evaluated11Degraded and failure modes, Identity and attribution, Network traffic, SDK and framework seamsS6 S9 S13 N4 I1 I2 I3 I5 I7 G5 G8
Denied before execution13Degraded and failure modes, Developer-tool launch, Host actions, MCP, Network traffic, SDK and framework seamsS1 S2 S5 S8 H5 N1 N2 N3 M1 M3 L1 G1 G3
Redacted5Credentials, Degraded and failure modes, MCP, Network trafficN7 M9 C1 C2 G4
Approval required0no row in the manifest carries this term
Degraded1Degraded and failure modesG6
Unmeasured36Credentials, Degraded and failure modes, Developer-tool launch, Host actions, Identity and attribution, MCP, Network traffic, SDK and framework seamsS7 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
Experimental1Platform host-level interceptionP1
Planned0no row in the manifest carries this term
Unsupported6Developer-tool launch, MCP, Network traffic, Platform host-level interceptionN8 N11 M8 L4 P3 P4
Total80

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

AreaRowsOutcomes its rows reach today
SDK and framework seams (sdk)13Observed 2 · Evaluated 3 · Denied before execution 4 · Unmeasured 4
Network traffic (network)13Evaluated 1 · Denied before execution 3 · Redacted 1 · Unmeasured 6 · Unsupported 2
MCP (mcp)10Denied before execution 2 · Redacted 1 · Unmeasured 6 · Unsupported 1
Host actions (host_action)8Observed 1 · Detected 1 · Denied before execution 1 · Unmeasured 5
Developer-tool launch (devtool_launch)8Observed 1 · Denied before execution 1 · Unmeasured 5 · Unsupported 1
Credentials (credentials)6Detected 1 · Redacted 2 · Unmeasured 3
Identity and attribution (identity)7Evaluated 5 · Unmeasured 2
Degraded and failure modes (degraded_mode)11Evaluated 2 · Denied before execution 2 · Redacted 1 · Degraded 1 · Unmeasured 5
Platform host-level interception (platform)4Observed 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.

PlatformCapability rows released on itHost-level interception todayReachability of that row
Linux x86_6471Experimental (P1)shipped on crates.io only
Linux aarch6470Observed (P2)shipped on crates.io only
macOS64Unsupported (P3)shipped on crates.io only
Windows13Unsupported (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.

ChannelRows delivered on itRows recorded as not published thereRows recorded as not surveyed
GitHub Release assets (github_release)50not recordednot recorded
Homebrew tap (homebrew)50not recordednot recorded
Install script (install_script)50not recordednot recorded
crates.io (crates_io)73not recordednot recorded
PyPI (pypi)13not recordednot recorded
npm (npm)13not recordednot recorded
Go modules (go_modules)13not recordednot recorded
GHCR container images (ghcr)243217
No distribution question (not_applicable)7not recordednot 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 rowRows
At least a located test26
No located test, but a test asserted but not locatable from the manifest’s repository10
A recorded gap — no test44
Does that evidence run?Rows
It runs on every push to main42
It is path-gated, with a schedule5
It does not run33

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.py from capability-surface.toml. Do not hand-edit between the BEGIN/END GENERATED markers — 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.

SourceWhat it decides for this page
The capability and evidence manifest (AAASM-5531), in the agent-assembly repositoryEvery row in every table below
ADR 0033 §1The roles the three paths are named after, and which components implement each
ADR 0033 §6The words for what a path did to an action. This page prints them and does not define them
What ships todayThe 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.yaml in the ai-agent-assembly/agent-assembly repository — the capability and evidence manifest defined by AAASM-5531 — through the pinned extract capability-surface.toml in this repository.

Manifest version 1.0.0 · 80 capability rows · taken at commit e2730ddaf · the manifest’s own evidence tree 299de3883, 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.

PathImplemented today byRowsOutcomes its rows reachRows at Unmeasured or Unsupported
Managed execution checkpointsthe SDK seams, aa-runtime’s handle_policy_query, aa-sdk-client and aa-sandbox17Observed 2 · Evaluated 4 · Denied before execution 6 · Unmeasured 55 of 17
Protocol and transport mediationaa-proxy26Evaluated 1 · Denied before execution 6 · Redacted 5 · Unmeasured 11 · Unsupported 314 of 26
Platform-specific host-level interception adaptersLinux eBPF via aa-ebpf-loaderd; on macOS and Windows, no adapter11Observed 2 · Detected 1 · Degraded 1 · Unmeasured 4 · Experimental 1 · Unsupported 26 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 bythe SDK seams, aa-runtime’s handle_policy_query, aa-sdk-client and aa-sandbox
Manifest rows17 — S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 H5 G1 G2 G5
Outcomes its rows reachObserved 2 · Evaluated 4 · Denied before execution 6 · Unmeasured 5
Decision timingbefore the action takes effect 12 · after the action 2 · no decision point 3
Failure posturefail closed 9 · fail open 1 · fail open, and silently 2 · no failure posture 5
Reachabilityshipped 17
Default state of its controlson 5 · off 2 · open 2 · closed 3 · mixed 1 · not_applicable 4
Declared preconditionsenv:AA_AGENT_ID
Launch paths that reach itaasm 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.

IdWhat it isADR 0033 §6 termReachability
S7Node default mode routes every policy check through an allow-all no-op clientUnmeasuredshipped
S10Direct function call that does not pass a patched seamUnmeasuredshipped
S11Framework with no adapterUnmeasuredshipped
S12Raw HTTP, subprocess, filesystem, DB driver, browser automation from inside an SDK-adopting processUnmeasuredshipped
G2aa-runtime with no gateway configured, or fail_closed=falseUnmeasuredshipped

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 byaa-proxy
Manifest rows26 — 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 reachEvaluated 1 · Denied before execution 6 · Redacted 5 · Unmeasured 11 · Unsupported 3
Decision timingbefore the action takes effect 5 · in line with the request 8 · no decision point 13
Failure posturefail closed 10 · fail open 6 · silent truncation 1 · no failure posture 9
Reachabilityshipped 3 · shipped, with a platform exception 20 · no mechanism exists 3
Default state of its controlson 6 · off 4 · open 3 · closed 1 · not_applicable 12
Declared preconditionsenv: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 itany · 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.

IdWhat it isADR 0033 §6 termReachability
N5HTTPS to a host not under MitMUnmeasuredshipped, with a platform exception
N6Model response body scanning on LLM hostsUnmeasuredshipped, with a platform exception
N8HTTP/2, gRPC or WebSocket over a MitM’d hostUnsupportedshipped, with a platform exception
N9Chunked transfer encodingUnmeasuredshipped, with a platform exception
N10Raw TCP that does not speak the proxy protocolUnmeasuredshipped
N11UDP, QUIC, HTTP/3Unsupportedshipped
N12Local IPC (Unix domain sockets) between third-party processesUnmeasuredshipped
M2MCP enforcement with no gateway configuredUnmeasuredshipped, with a platform exception
M4Every MCP method other than tools/callUnmeasuredshipped, with a platform exception
M5MCP over stdio (subprocess pipes)Unmeasuredno mechanism exists
M6MCP over SSE (text/event-stream)Unmeasuredno mechanism exists
M7MCP over Streamable HTTPUnmeasuredshipped, with a platform exception
M8MCP over WebSocketUnsupportedno mechanism exists
C4Model response credential scanningUnmeasuredshipped, 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 byLinux eBPF via aa-ebpf-loaderd; on macOS and Windows, no adapter
Manifest rows11 — H2 H3 H4 N13 I4 G6 G7 P1 P2 P3 P4
Outcomes its rows reachObserved 2 · Detected 1 · Degraded 1 · Unmeasured 4 · Experimental 1 · Unsupported 2
Decision timingin line with the request 1 · after the action 7 · no decision point 3
Failure posturefail closed 1 · fail open 8 · fail open, and silently 1 · no failure posture 1
Reachabilityshipped on crates.io only 10 · no mechanism exists 1
Default state of its controlson 6 · off 2 · open 2 · not_applicable 1
Declared preconditionsenv:AA_EBPF_CONFINE_PID
Launch paths that reach itAA_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.

IdWhat it isADR 0033 §6 termReachability
H3Process exec observationUnmeasuredshipped on crates.io only
N13TLS plaintext observation without the proxyUnmeasuredshipped on crates.io only
I4Process-tree identity across fork/execUnmeasuredshipped on crates.io only
G7eBPF policy file unreadable or unparseableUnmeasuredshipped on crates.io only
P3macOS host-level interceptionUnsupportedshipped on crates.io only
P4Windows mediationUnsupportedno 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.

IdRouteADR 0033 §6 termDecision timingFailure posture
H8Shell / file rule declared in a tool’s own settings fileUnmeasuredbefore the action takes effectfail open, and silently
M10MCP-server governance by configurationUnmeasuredbefore the action takes effectfail open, and silently
L1Claude Code managed launchDenied before executionbefore the action takes effectfail closed
L2Codex managed launchUnmeasuredbefore the action takes effectfail open, and silently
L3Windsurf managed launchUnmeasuredbefore the action takes effectfail open, and silently
L4Copilot managed launchUnsupportedno decision pointno failure posture
L5SaaS / opaque agentObservedafter the actionno failure posture
L6Unmanaged launch — the user starts the tool directlyUnmeasuredno decision pointno failure posture
L7Settings-layer governance surviving an unmanaged launchUnmeasuredbefore the action takes effectfail open, and silently
L8aasm run –no-proxyUnmeasuredno decision pointno 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.

AreaRowsOutcomes they reachCapability ids
Host actions (host_action)3Unmeasured 3H1 H6 H7
Credentials (credentials)3Detected 1 · Unmeasured 2C3 C5 C6
Identity and attribution (identity)6Evaluated 5 · Unmeasured 1I1 I2 I3 I5 I6 I7
Degraded and failure modes (degraded_mode)4Evaluated 1 · Unmeasured 3G8 G9 G10 G11
Total16

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.py from capability-surface.toml. Do not hand-edit between the BEGIN/END GENERATED markers — 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:

SourceWhat it suppliesWhere
ADR 0033 — Canonical Governance & Enforcement ArchitectureThe 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 inventory69 audited rows of what may and may not be said, each tied to a re-verified evidence block (E1E7).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 GlossaryADR 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.md is named in ADR 0033’s migration checklist and is covered by AAASM-5586 / AAASM-5609. glossary.md was named by neither — not by the checklist and not by the claim inventory, whose docs rows reach only README.md and comparison.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.

ClauseADR 0033 §6 termWhy the term fits
“decides whether an … action is allowed”EvaluatedThe control plane produces a decision for the action; a decision record exists.
“before that action runs”Denied before executionReached 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”ObservedAn 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 executionAs above.
“blocked pending a decision”Approval requiredThe 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 holdsOff 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 inspectionNarrow. 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 listsEmpty. 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 handlingRedact and forward. Blocking on a detected credential is opt-in. Model responses are not scanned.
SDK enforcementOff 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.
eBPFOff 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 sessionRefused. 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 fallthroughAllow. 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 capsNone 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.
AuditOn, 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.

MechanismHighest ADR 0033 §6 term it reaches today
Proxy — CONNECT, in-tunnel host re-check, credential block, MCP adjudicationDenied before execution, for traffic routed through it and intercepted
Gateway check_actionEvaluated; 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 checkpointEvaluated; Denied before execution only if the SDK shim honours the answer
Runtime scannerRedacted — it runs after the action and returns counters, not a verdict
SDK clientEvaluated (advisory)
eBPF TLS / file / exec probesObserved / Detected
eBPF syscall guardDetected, plus asynchronous process termination
WASM sandboxDenied 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 wordingMust 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-executionDenied 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
EvidenceObserved“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 controlsObserved / 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 actionEvaluated“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
PlannedPlanned“planned — decided, not built yet”, with the ticket reference“coming soon”; “available in Enterprise”; a roadmap item written in the present tense
Not inspectedUnmeasured“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.

RejectedWhyInventory 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” coverageEach 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 postureADR 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 architectureA 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. E1E7 are the evidence blocks in the AAASM-5528 inventory; §n refers to ADR 0033.

Statement§6 termEvidence
The decision happens before the action runs, where the refusing component sits in front of itDenied before execution§2 caller table; §6 mechanism table; E7
The control plane decides but holds no trafficEvaluated§2
The proxy refuses before dialling upstreamDenied before executionE2 (CONNECT 403, in-tunnel host re-check, credential block, MCP adjudication)
The SDK is advisoryEvaluatedE3, 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 initEvaluatedVerified 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 timeoutApproval requiredGateway 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 refusalEvaluated → Denied before executionAtomic spend reservation inside the same decision path
llm_only defaults on; three built-in LLM hosts are interceptedUnmeasured (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 configurationE2 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-forwardRedactedE4
The audit chain is an unkeyed digest over the JSONL sink, verifiable in the open-source buildObservedE5
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 uncountedObserved, boundedRead 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 0Observed, boundedThe 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 asynchronouslyObserved / DetectedE1, §5.1
Windows has no local mediationUnsupported§5.3
An unrouted action is not inspectedUnmeasured§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.

StatementWhy it is provisionalOwner
“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 loopThe 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 throughEmission 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 missingAAASM-5626
Any coverage figure — a percentage, a count of governed actions, a fleet-level numberThere 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 releaseThe 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 commitmentPlanned, 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:

  1. 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.
  2. Keep the boundary clause above the fold. Not a footnote, not a tooltip, not a “learn more”.
  3. Pick a §6 term for every verb. If the sentence works with “protects”, “enforces” or “catches”, it is not yet specific enough to publish.
  4. 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.
  5. 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.
  6. 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.

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

FieldWhat it answers
Threat sourceWho or what causes the action. Usually not malice — a plausible instruction, an injected one, or a loop.
Requested actionThe concrete thing the agent tries to do.
Existing-system gapWhy the tools already in place do not stop it.
Governed pathWhat the operator did to put this action in front of a decision. Routing is a thing you do, per agent and per launch.
Policy decisionAn ADR 0033 §6 term, plus which component actually decided. The right guarantee attributed to the wrong component is its own defect class.
Prevented outcomeThe specific effect that did not occur — stated as an absence, not as an error.
EvidenceAn ADR 0033 §6 term, plus the test or record that substantiates it.
Known boundaryDefaults, 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

#ScenarioMechanism§6 term reachedDetermination5527 rows
FFlagship — the upload that never happenedaa-proxy CONNECT-time egress refusalDenied before executionExecutable, default-off (Linux; macOS crates.io only)N1, N2, L1
T1Secret exfiltrationaa-proxy outbound credential scan on inspected LLM hostsRedactedExecutable today (redact-and-forward is the default, not block)C1, C6, N3 · C2 default-off · C3 dead code
T2Destructive production actionGateway adjudication of an MCP tools/callDenied before executionExecutable, default-off — HTTP/1.1 POST only; stdio, SSE and Streamable HTTP all outside itM1, M3 · M5/M6 absent · M7 broken
T3Runaway costGateway budget reservationEvaluated (reaches Denied before execution only through a blocking caller)Executable today, with a silent fail-open(no positive row — see the scenario) · G9
T3bUnauthorized paymentIllustrative — 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 sourceNot 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 actionAn outbound HTTPS connection from the agent’s process to a host outside the approved set, carrying the contents of the working tree.
Existing-system gapThe 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 pathThe 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 decisionDenied 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 outcomeNo TCP connection to the destination is established, so no byte of the working tree leaves the machine by that route.
EvidenceObserved — 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 boundarySee below — six parts, all load-bearing.

Known boundary, in full — six parts, all load-bearing:

  • The lists are empty by default. AA_PROXY_DENIED_HOSTS and AA_PROXY_NETWORK_ALLOWLIST are both empty out of the box (aa-proxy/src/config.rs:75-85), and the row’s default_state is 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-proxy ships in the GitHub Release, the Homebrew tap and the install script for linux_x86_64 and linux_aarch64 only — the release workflow states the proxy is a Linux-only component. On macOS the sole route is cargo install aa-proxy from 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 at aa-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, default false at :184). No environment variable does this; from_env hardcodes 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-proxy cannot 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.

  1. Positive control — the check can see the effect. With the destination host allow-listed, run the same agent action. Assert listener.accepted_connections == 1 and 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.

  2. Negative run — the effect is absent. With the destination denied, run the same agent action, then assert in this order:

    1. First: listener.accepted_connections == 0 and listener.bytes_received == 0. This is the load-bearing assertion and it must execute before anything that can throw, return early, or short-circuit.
    2. Then: the caller observed a refusal (403).
    3. Then: a decision record exists attributing the refusal to the proxy’s CONNECT-time egress check. The harness must set AA_PROXY_AUDIT_JSONL_PATH for 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.
  3. 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 sourceAn 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 actionAn outbound HTTPS request to a model provider whose body or headers carry a credential.
Existing-system gapThe 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 pathTraffic routed to the proxy and the proxy’s CA trusted, so the request is inspected rather than tunnelled.
Policy decisionRedacted. 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 outcomeThe recognised credential does not reach the provider in cleartext. Not “the request was stopped” — it was forwarded with the credential removed.
EvidenceObserved — 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 boundarySee 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_only defaults on, which intercepts api.openai.com, api.anthropic.com and api.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 on sk- while Stripe uses sk_. 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 on main (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 its default_state is false (the operator must set AA_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 sourceAn 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 actionAn MCP tools/call carrying a destructive operation — a DROP TABLE, a migration that rewrites rows, a delete against a production namespace.
Existing-system gapTool 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 pathTraffic 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 decisionDenied 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 outcomeThe tools/call envelope is not forwarded. The MCP server never receives the call, so the table is not dropped.
EvidenceObserved — 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 boundarySee 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 POST with an explicit Content-Length. Every other MCP transport is outside it, and M1’s own known_bypasses says 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 HTTPcoverage: unmeasured, and worse than uncovered (M7). Its failure_posture is silent_truncation: the client receives an empty 200. aa-proxy/src/proxy/http.rs:13 claims 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).
  • Off by default, and the shipped CLI routes couple it to whole-machine interception — but the mechanism does not. The row’s default_state is 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) unions mitm_hosts, so AA_PROXY_GATEWAY_ENDPOINT together with AA_PROXY_MITM_HOSTS, leaving llm_only on, 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 notes still says “the only supported route … forces AA_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 Pending decision is downgraded to Deny inside 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_quality is unit_only — the wire-level test e2e_mcp_interceptor.rs has no batch case — and mentions_tools_call inspects only one level (aa-proxy/src/intercept/mcp.rs:128-130), so a nested batch, or one whose elements carry params without a literal top-level method, 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 sourceNo adversary at all. A retry loop, a recursive plan, or a fan-out over more files than anyone estimated.
Requested actionThe next model call, after the team’s declared spend cap has been reached.
Existing-system gapProvider 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 pathAn action evaluated by the gateway, under a policy that declares a budget.
Policy decisionEvaluated. 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 outcomeConditional, 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.
EvidenceObserved — 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 boundarySee 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’s failure_posture is fail_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 a tracing::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/call on 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 obvious test does not prove this scenario. aa-integration-tests/tests/e2e_budget.rs looks like the evidence and is not: all of its tests drive record_raw_spend directly and assert on the returned BudgetStatus, never calling check_action or the policy engine — its own header records the substitution at :15. record_raw_spend accounts 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 evidence field is marked GAP. 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 -E does not support \b. A sweep written as \bcharge|\bspend matches nothing and looks exactly like a real absence. Any negative finding derived from a \b pattern 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/call that 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:

  1. 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.
  2. No scenario sentence on this page is approved for a metadata surface. A <title>, an og: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. Use product-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.
  3. 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

CriterionHow it is met
The flagship can be demonstrated with a negative control proving the denied side effect did not happenNegative 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 exercisedEvery 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 knowledgeNo 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 assetsApproved 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.

LayerNameWhat it does
1BoundaryNetwork 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
2IdentityAgent 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
3PolicyRuntime 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
4Vault🗺️ 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
5TelemetryAudit 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.

ComponentSpoofingTamperingRepudiationInfo DisclosureDenial of ServiceElevation of Privilege
Language SDKOne-time Ed25519 possession-proof at registration, then a random per-agent credential token (constant-time compare) on every callPackage-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 runtimeCalls 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 loggedBudget 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 ratePolicy 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 defaultPer-service decoded-message-size caps; policy documents are schema-validated and reject unknown keys fail-closed. This is not blanket input validation on every RPCJSONL 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 interceptorPer-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 limitRBAC 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 allOn 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 itProxy 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 cacheOn 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 errorBPF verifier rejects unsafe programs at load timeKernel 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 assessmenteBPF programs have bounded execution; the verifier rejects a program with an unbounded loopThe 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 SSOMalformed 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 checkMutating 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-assembly repository.


Cryptographic primitives

PrimitiveAlgorithmKey lengthUsageRotation cadence (NIST SP 800-57)
Agent registration proofEd25519256-bitOne-time possession-proof signature over a server-issued nonce, verified at RegisterAgent; not a reusable bearer credentialAgent-supplied keypair; not gateway-managed
Agent credential tokenUUID v4 (CSPRNG)122-bit randomBearer credential presented on every agent-plane gRPC call after registration; validated with a constant-time compareNo expiry — replaced only on re-registration
REST/admin session tokenJWT (HMAC-SHA256)256-bitAuthenticates REST/admin API callers; only issued when gateway auth is explicitly enabled (off by default)24h token TTL
Vault encryptionRemoved. No AES-256-GCM implementation exists in the workspace crates — see Secrets management
Webhook signature verification (inbound)HMAC-SHA256256-bitVerifies 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 onlyShared secret is issued and rotated by the sending provider, not by this software
TLS (transport)TLS 1.3ECDHE-256Operator/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-gateway boots with AuthMode::Off on its REST/admin surface — the zero-config aasm status path (and any other REST/admin route) is served with no credential until an operator explicitly opts in with AA_GATEWAY_AUTH=on and a valid AA_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 PolicyService or AgentLifecycleService; those two are mounted with an enrichment interceptor that never rejects. That does not make them open: their handlers validate directly. Register requires the agent_id’s did:key to encode the supplied public key (rejected as Unauthenticated otherwise), 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, Deregister and the control stream validate the token, and CheckAction rejects 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, or aa-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 SecretsStore trait 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 secrets command. Every ${NAME} placeholder therefore resolves to UnknownPlaceholder and the request fails (HTTP 422 / gRPC FailedPrecondition).
  • 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 single gateway-default.jsonl that 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.0 agent-assembly source.

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.


Evaluating for production? Request Cloud Early Access →

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.

CapabilityAAASMLangfuseHeliconeOpikPillar Security
Observability
LLM call tracing (latency, tokens, cost)partial
Multi-turn conversation tracingpartial
Agent lineage / parent-child spanspartial
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 / outputspartial (regex policy)
Multi-language SDK
Python SDK
TypeScript SDKpartial
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)partialpartialpartial
SAML 2.0 / OIDC SSO✓ 🚧partial (Enterprise)partial (Enterprise)partial (Enterprise)
SCIM user provisioning✓ 🚧partial
Approval workflows
Human-in-the-loop approval gatespartial (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 3partial (unkeyed SHA-256 chain over the JSONL sink)partial
Audit log retention > 30 days✓ 🚧 (up to 1 year, Enterprise)partial (30 days free)partialpartial
On-premises / self-hosted option
Self-hosted deploymentpartial (limited-function OSS)✗ (SaaS only)

Where we currently lag

These are capabilities competitors offer that AI Agent Assembly does not yet fully deliver.

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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.

  1. 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.
  2. Kernel-level bypass detection via eBPFaa-ebpf reads 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.
  3. Network-layer interception without agent code changesaa-proxy performs 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.
  4. 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.
  5. 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-chain re-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.

CompetitorDocumentation URL
Langfusehttps://langfuse.com/docs
Heliconehttps://docs.helicone.ai
Opikhttps://www.comet.com/docs/opik
Pillar Securityhttps://docs.pillar.security


Last reviewed: 2026-08-06 — AI Agent Assembly Team


Last updated: 2026-09-07 by AI Agent Assembly Team


  1. No change to your agent’s code, but the agent process must be launched so that it honours HTTP_PROXY/HTTPS_PROXY and trusts the proxy’s local root CA. On macOS the install is attempted at proxy start and shells out to security add-trusted-cert, which requires admin authorization — macOS prompts, and a refusal fails proxy startup. On Linux run sudo 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.

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

  3. 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-chain re-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.

AreaIn the Apache-2.0 core
InterceptionLanguage SDKs (Python, TypeScript, Go); sidecar proxy (aa-proxy); eBPF sensor (aa-ebpf, Linux)
Gateway and policyAgent registry; policy engine (allow/deny/audit); policy-as-code (YAML/JSON); budget limits declared in policy and enforced by the gateway — see Policy reference
AuthenticationAPI key authentication
AuditAudit event emission and query — see Security model
Operationsaasm 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:

CrateLicenseNotes
aa-coreApache-2.0Core domain types — always OSS
aa-protoApache-2.0Protobuf definitions — always OSS
aa-runtimeApache-2.0Async runtime utilities — always OSS
aa-gatewayApache-2.0Gateway with policy engine — always OSS
aa-apiApache-2.0REST API surface — OSS
aa-proxyApache-2.0Sidecar proxy — always OSS
aa-ebpfApache-2.0eBPF user-space loader — always OSS
aa-ebpf-commonApache-2.0eBPF shared types — always OSS
aa-wasmApache-2.0WebAssembly build — always OSS
aa-cliApache-2.0aasm operator CLI — always OSS
conformanceApache-2.0Conformance 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:

CrateRepoLicenseNotes
aa-ffi-pythonpython-sdk (native/aa-ffi-python)MITPython SDK native shim — the python-sdk repo is intentionally MIT, not Apache-2.0
aa-ffi-nodenode-sdk (native/aa-ffi-node)Apache-2.0TypeScript SDK native binding
aa-ffi-gogo-sdk (native/aa-ffi-go)Apache-2.0Go 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.



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 thisGo here
Run the gateway, policy engine, proxy, or CLIcore docs
Instrument a Python agentPython SDK docs
Instrument a TypeScript agentNode SDK docs
Instrument a Go agentGo SDK docs
Run a limited-function stack locally with Docker ComposeDocker & containers
Read the policy rule schema the gateway evaluates againstPolicy reference
Step through a working governed agent end to endexamples 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-META carries a single ADR 0033 §6 Planned claim, with platforms: [] and no availability value — 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 cloud repository is private and outside this hub’s public content boundary. A design deep-dive here would describe a system no reader can verify.

Next step Run a working example →

Open the examples repo and step through a governed LangChain, LlamaIndex, or bare-OpenAI agent end-to-end.

Interested in a managed workspace? Register interest →

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.

ConcernWhere it is documented today
Running the gateway, proxy, sensor, and CLIcore docs
Bringing up a limited-function stack with Docker ComposeDocker & containers
Health probes and Prometheus metrics for that stackSelf-host observability
The policy rule schema the gateway evaluates againstPolicy reference
Spend capsPolicy reference → budget — per-agent and per-organisation USD limits declared in policy
Authentication that exists todayAPI keys, as described in Open core boundary
Which capabilities are open source and which are intended for the commercial tierOpen 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-META carries a single ADR 0033 §6 Planned claim, with platforms: [] and no availability value — 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 cloud repository is private and outside this hub’s public content boundary. A design deep-dive here would describe a system no reader can verify.


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.

LanguageSDK documentationContent layerComponentDocumentation-area maturity
PythonPython SDK docsL3python-sdk🧪 Release candidate
TypeScript / JavaScriptNode SDK docsL3node-sdk🧪 Release candidate
GoGo SDK docsL3go-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 questionWhere it is answeredContent layer
1How do I start an agent under this?core documentationL3
2How do I install it on this platform?Docker and containers · core documentationL2 · L3
3What does the running stack expose?Self-host observabilityL2
4Why did a control not fire?TroubleshootingL2

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:

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

ImageRole
ghcr.io/ai-agent-assembly/aa-gatewayThe policy/registry brain. Loads a policy file and serves the gRPC API on :50051AgentLifecycleService.Register (agent registration) and PolicyService.CheckAction (the per-tool allow/deny decision). This is the policy authority.
ghcr.io/ai-agent-assembly/aa-runtimeThe 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/pythonSDK 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/nodeSDK base image for building a Node/TypeScript agent — ships node, the globally installed @agent-assembly/sdk, and aasm.
ghcr.io/ai-agent-assembly/goSDK 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) and aa-proxy (the egress-interception proxy) are Apache-2.0 crates in the agent-assembly repository but have no published container image — do not expect to docker pull them. There is no separately runnable aa-api container: 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 :50051 and no HTTP (see Self-Host Observability). To run aa-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:

ImagePinned (release) tagsMoving tags
python3.12-slim-v0.0.1-rc.6, 3.13-slim-v0.0.1-rc.6, 3.14-slim-v0.0.1-rc.63.12-slim, 3.13-slim, 3.14-slim, latest
node20-slim-v0.0.1-rc.6, 22-slim-v0.0.1-rc.6, 24-slim-v0.0.1-rc.620-slim, 22-slim, 24-slim, latest
go1.24-alpine-v0.0.1-rc.6, 1.25-alpine-v0.0.1-rc.6, 1.26-alpine-v0.0.1-rc.61.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
AspectValue
Args--policy <path> (section-based policy file), --listen <addr>
Port50051 — gRPC (Register + CheckAction)
Volumethe policy file, mounted read-only
Userruns 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 varPurpose
AA_AGENT_IDAgent identity — determines the socket name /tmp/aa-runtime-<AA_AGENT_ID>.sock. Must match the agent’s AA_AGENT_ID.
AA_GATEWAY_ENDPOINTGateway 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_ADDRBind address for the health/metrics server. Default 0.0.0.0:8080.
AspectValue
Port8080 — HTTP /health, /ready, /metrics
Volumea shared /tmp volume so the agent can reach the IPC socket
Userruns non-root (uid 65532)

Health-checking

ComponentProbeEndpoint
aa-runtimeliveness / readiness / metricsGET /health, GET /ready, GET /metrics on :8080
aa-gatewayTCP livenessTCP 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 :50051 and exposes no HTTP health endpoint — there is no /healthz to curl on this container. On top of that the image is distroless (no shell, no curl/wget inside), so a Compose healthcheck: ["CMD-SHELL", …] cannot run inside it either. Probe it from the host with a TCP connect to the gRPC port :50051 instead. A standard gRPC grpc.health.v1.Health service on :50051 is being added (AAASM-4759); until it ships, a TCP-level check of :50051 is 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-runtime and aa-gateway ship no shell, gate readiness from the host (poll the runtime’s :8080/ready and the gateway’s :50051) rather than with a Compose CMD-SHELL healthcheck — an in-container probe fails with exec: "/bin/sh": no such file or directory and leaves depends_on: condition: service_healthy stuck.

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.



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

ComponentSurfaceDefault endpoint(s)Purpose
aa-runtimeHealth + 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 livenessgRPC port :50051Process-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:

SurfaceWhere it existsDefault endpoint
/healthzgateway in local or remote mode/healthz
/api/v1/healthgateway 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. Returns 200 OK with a JSON body reporting status, process uptime, events processed, and which enforcement layers are active or degraded. Use this as a liveness probe.
  • GET /ready — readiness. Returns 200 OK (body ready) once the runtime is ready to accept work, or 503 Service Unavailable (body not 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. Returns 200 OK with 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 same aa-gateway process (there is no separate aa-api container). Returns 200 OK when every subsystem check passes, or 503 Service Unavailable when any is degraded; the JSON body includes the build version, api_version, uptime, and a checks map 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.

SettingValue
Env varAA_METRICS_ADDR
Default bind address0.0.0.0:8080
Metrics path/metrics
Scrape targethttp://<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.

MetricTypeWhat it represents
aa_events_received_totalcounterGovernance events the runtime has received
aa_events_emitted_totalcounterEvents the runtime has emitted downstream
aa_policy_violations_totalcounterPolicy violations observed
aa_policy_evaluations_totalcounterPolicy evaluations performed (currently reports 0; reserved for a forthcoming release)
aa_active_connectionsgaugeCurrently active connections
aa_channel_utilization_ratiogaugeInternal channel utilization ratio

Note: these six are the baseline surface. Only aa_active_connections and aa_channel_utilization_ratio are gauges; the other four are counters. None are histograms. Because they start at 0, 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.rsAA_METRICS_ADDR and its default.
  • aa-runtime/src/runtime.rs and aa-runtime/src/health/ — the health/metrics server and the baseline metrics.
  • aa-gateway/src/main.rs — the mode resolver (resolve_mode); the default is legacy-grpc, which runs serve_tcp (gRPC only, no HTTP health) unless --mode/AA_MODE selects local or remote.
  • aa-gateway/src/routes/healthz.rs — the /healthz liveness probe (local and remote modes only).
  • aa-api/src/routes/health.rs — the /api/v1/health check (mounted by the gateway in local mode; there is no separately runnable aa-api container).


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 seeCauseFix
pip install agent-assemblyERROR: Could not find a version that satisfies the requirement agent-assembly / No matching distribution foundOnly 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 gatewayOlder 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 runningConnecting 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 enforcementNeither 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 exampleRecent 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

SymptomLook here
SDK cannot register the agent / handshake failsPython SDK docs, Node SDK docs, Go SDK docs
Sidecar proxy (aa-proxy) drops connectionsCore docs — Proxy section
eBPF sensor (aa-ebpf) fails to loadCore docs — eBPF section
Policy YAML rejected at gatewayPolicy reference
Compatibility mismatch between core and an SDKCompatibility 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 to http://localhost:7391, a local gateway auto-started via aasm start if 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.

Still stuck? Open a GitHub issue on the core repo →

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 🗺️ Planned you 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 🗺️ Planned because its area is; an action is Planned in §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 checkRead
What a scenario does and does not demonstrateRisk scenarios, and its negative control
Wording that has already been reviewed for reuseRisk scenarios: approved wording
What may be said about the managed serviceSaaS claim publication checklist
Which areas are open source and which are notOpen core boundary
Who owns an area, and how mature its documentation isStatus 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:

ComponentSecurity policy
Coreagent-assembly/SECURITY.md
Python SDKpython-sdk/SECURITY.md
Node SDKnode-sdk/SECURITY.md
Go SDKno repository policy today; use the organisation policy
Arenano repository policy today; use the organisation policy
This documentation hubno 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

  1. Find the claim class you want to publish in the register below.
  2. Produce the evidence in the Evidence required column. Link it from the pull request.
  3. Get written approval from the role in the Approval owner column. An approval covers a specific wording, not a topic.
  4. 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.
  5. 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 removedWhy it was removedApproval ownerEvidence required to restore
Named uptime or availability percentages (for example a 99.5% or 99.9% monthly figure), per tierAsserted a measured, contractual availability level for a service that is not running, so no availability has ever been measuredHead of Engineering and LegalA 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 availabilityA financial remedy implies a contract; none is publishedLegal and FinancePublished 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 itHead of Support and LegalA 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 servingHead of SupportThe channel reachable at a published URL, with a named owner and a stated scope
A dedicated named engineering or SRE contact per customerA staffing commitment with no staffing model behind itHead of Engineering and Head of SupportA defined role with allocated headcount, and the commitment written into published terms

Plans, quotas, and pricing

Claim removedWhy it was removedApproval ownerEvidence required to restore
Plan and tier names presented as purchasable, and what each plan includesPresented a commercial catalogue that cannot be boughtProduct Lead and FinanceAn approved and published pricing page; a working purchase path; the plan-to-entitlement mapping enforced by the running service
Prices, currencies, and billing periodsNo published price list existsFinance and LegalAn approved price list and published terms covering it
Numeric quotas — maximum agents, maximum policies, retention periods per planPublished numbers that the service does not enforceProduct Lead and Head of EngineeringThe limit enforced by the running service, and a test demonstrating the enforced value matches the documented one
“Unlimited” for any resourceAn unqualified absolute; every real system has a limitProduct Lead and Head of EngineeringEither a stated numeric limit, or an explicit statement of what bounds the resource in practice

Regions and data residency

Claim removedWhy it was removedApproval ownerEvidence required to restore
A list of available regions, and per-region locationsPresented a deployment footprint that does not existHead of InfrastructureThe 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 itHead of Infrastructure and LegalA technical control enforcing the boundary; an audit demonstrating no cross-region egress of customer data; the guarantee written into published terms
Dedicated single-tenant regionsAn isolation guarantee with no isolation implementationHead of Infrastructure and LegalThe isolation model documented and independently reviewed; the guarantee written into published terms
Region migration on requestAn operational procedure implying an operations team and a runbookHead of InfrastructureA tested migration runbook, and a named team accountable for running it

Onboarding and account operations

Claim removedWhy it was removedApproval ownerEvidence required to restore
Signup and contact-sales URLs presented as workingThe referenced endpoints were not servingProduct LeadThe 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 productProduct Lead and DesignThe console reachable in production, and the documented navigation path verified against the shipped UI on the day of publication
A tenant or workspace identifier formatPublished a specific string format that the service does not produceHead of EngineeringThe format emitted by the running service, and a test asserting it
Onboarding-duration estimates (“about 10 minutes”, “about 30 minutes”)An unmeasured performance claimProduct LeadA 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 runningHead of Sales and LegalA defined procurement process with a named owner, and observed durations across completed deals

Billing

Claim removedWhy it was removedApproval ownerEvidence required to restore
A named payment processor and card-billing instructionsNamed a specific processor that is not integrated, and instructed readers to enter card details on a page that does not existFinance and Head of EngineeringThe processor integrated in production; a completed test transaction; the instructions verified against the shipped flow
Invoicing behaviour — issue cadence, recipients, deliveryDescribed an invoicing operation that does not runFinanceInvoices issued in production, and a named owner for the billing operation
Payment terms (for example net-30), purchase orders, wire or ACH acceptanceContractual payment terms with no published contractFinance and LegalPublished terms containing the payment terms, countersigned
Payment-method management instructionsDescribed console screens that are not reachableFinance and Product LeadThe flow reachable in production and verified on the day of publication

Identity, provisioning, and access control

Claim removedWhy it was removedApproval ownerEvidence required to restore
SSO configuration walkthroughs (SAML 2.0 and OIDC), including console paths, endpoints, and attribute mappingsInstructed readers through screens that are not reachable, for protocol support that must be confirmed protocol by protocol rather than asserted as a pairHead of Engineering and Product LeadThe 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 matrixPublished a per-operation support matrix that was not verified operation by operationHead of Engineering and Product LeadEach 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 tablePublished role names and permissions that did not match the intended productHead of Engineering and Product LeadThe role set and permissions read from the running service, with authorization tests as evidence
Group-to-role mapping instructionsDescribed configuration screens that are not reachableHead of EngineeringThe mapping configurable in production and verified end to end

Audit and security posture

Claim removedWhy it was removedApproval ownerEvidence required to restore
“Tamper-evident” or “immutable” audit log, unqualifiedA 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 completenessHead of Security and Head of EngineeringFor 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 sinkThe 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 themHead of EngineeringChain 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 itHead of Security and Head of EngineeringA 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 deletionHead of EngineeringAn 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 constructedHead of EngineeringA 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 rightHead of EngineeringEach listed category exercised end to end, with a test asserting an entry is persisted for it
Configurable audit-log retention, and retention periods per planPublished retention durations that the service does not enforce per planHead of Engineering and Product LeadThe retention period enforced by the running service per plan, with a test asserting it
CEF export, and “SIEM integration” as a managed capabilityCEF 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 rowHead of EngineeringFor 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 capabilityThe described form did not exist, and the fields did not match the budget schema the gateway enforces — see Policy referenceHead of Engineering and Product LeadThe managed configuration path working in production, and its fields reconciled against the enforced policy schema
Claim removedWhy it was removedApproval ownerEvidence 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 themLegal and Head of SecurityThe 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 rowLegal and Head of SecurityA 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 evidenceA formatting feature that prepends a header is not an attestation, and citing it as one inflates a build flag into a certificationHead of SecurityNothing 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 requestedLegalThe 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 assessedLegal and Head of SecurityThe executed template approved by counsel, and the underlying compliance evidence the agreement depends on
Countersignature and legal-review workflow descriptionsDescribed a legal operation that is not runningLegalA defined process with a named owner
A named commercial licence for the non-open-source capabilitiesNamed a licence whose terms are not published anywhereLegalThe licence text published, with a version and effective date
A “legal approver” attribution in a page footerAttributed legal sign-off to a page whose content had since changedLegalSign-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.

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

BlockWhat it controlsCore section
Document formatsEnvelope vs. flat YAMLDocument formats
Top-level fields, scopeWhich agents a policy applies to, cascade orderTop-level fields
networkOutbound domain allowlistingnetwork
scheduleActive-hours time windowsschedule
budgetSpend caps, currency, reset behaviourbudget
dataSensitive-data detection and redactiondata
toolsPer-tool allow/deny/approval and rate limitstools
capabilitiesCoarse-grained capability grantscapabilities
approvalEscalation overridesapproval

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.


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 Integrated and still carry no coverage evidence; see the manifest’s own governance/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

IDCapabilityOwnerFramework / toolPlatformReleased channelsCoverageProtection state
C1Outbound credential scan and redact on an inspected requestaa-securityaa-proxy, aa-securitylinux, macoscrates_io, github_release, homebrew, install_scriptRedactedNot applicable
C2Credential substitution at egress — the real provider key never enters the agentaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptRedactedNot applicable
C3Credential injection via SecretsService.DispatchToolaa-apiaa-api, aa-gatewaylinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
C4Model response credential scanningaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
C5Environment inheritance by aasm runaa-cliaa-clilinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
C6Credential scanner recallaa-securityaa-securitylinux, macos, windowscrates_io, github_release, homebrew, install_scriptDetectedNot applicable
G1aa-runtime to gateway unreachable on a policy queryaa-runtimeaa-runtimelinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptDenied before executionNot applicable
G10Audit emission failureaa-gatewayaa-gatewaylinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
G11Degradation visibility to a useraa-runtimeaa-api, aa-runtime, dashboardlinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
G2aa-runtime with no gateway configured, or fail_closed=falseaa-runtimeaa-runtimelinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
G3aa-proxy to gateway unreachable for MCP adjudicationaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
G4Credential and DLP default actionaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptRedactedNot applicable
G5SDK cannot reach the aa-runtime UDS socketSDK fail-closed posture on an unreachable runtimego-sdk, node-sdk, python-sdklinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiEvaluatedNot applicable
G6eBPF load or attach failureaa-runtimeaa-ebpf, aa-runtimelinuxcrates_ioDegradedNot applicable
G7eBPF policy file unreadable or unparseableaa-runtimeaa-runtimelinuxcrates_ioUnmeasuredNot applicable
G8Gateway policy load or schema failureaa-gatewayaa-gateway, aa-runtimelinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptEvaluatedNot applicable
G9Budget state unreadable or corruptaa-gatewayaa-gatewaylinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
L1Claude Code managed launchaa-devtool-claude-codeclaude_codemacoscrates_io, github_release, homebrew, install_scriptDenied before executionHost-enforced
L2Codex managed launchaa-devtool-codexcodexlinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredIntegrated
L3Windsurf managed launchaa-devtool-windsurfwindsurflinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredIntegrated
L4Copilot managed launchaa-devtool-copilotgithub_copilotlinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnsupportedIntegrated
L5SaaS / opaque agentaa-devtool-saasclaude_ai_and_siblingsnot_applicablecrates_io, github_release, homebrew, install_scriptObservedNot measured
L6Unmanaged launch — the user starts the tool directlynoneanylinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
L7Settings-layer governance surviving an unmanaged launchaa-devtool-claude-codeclaude_codemacoscrates_io, github_release, homebrew, install_scriptUnmeasuredIntegrated
L8aasm run –no-proxyaa-clianylinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptUnmeasuredNot applicable
H1Shell command / subprocess spawn by a native agent processnoneanylinux, macos, windowsnot_applicableUnmeasuredNot applicable
H2Shell command intercepted by the eBPF syscall guardaa-ebpf-probesanylinux_aarch64, linux_x86_64crates_ioDetectedNot applicable
H3Process exec observationaa-ebpf-probesanylinux_aarch64, linux_x86_64crates_ioUnmeasuredNot applicable
H4File read / write / unlink observationaa-ebpfanylinux_x86_64crates_ioObservedNot applicable
H5File access by a WASM-marked toolaa-sandboxaa-sandboxlinux, macos, windowscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
H6Browser action (Playwright / Selenium / Puppeteer)noneanylinux, macos, windowsnot_applicableUnmeasuredNot applicable
H7Database querynoneanylinux, macos, windowsnot_applicableUnmeasuredNot applicable
H8Shell / file rule declared in a tool’s own settings fileaa-devtool-claude-codeclaude_codemacoscrates_io, github_release, homebrew, install_scriptUnmeasuredIntegrated
I1Agent identity — Ed25519 did:key with a possession proofaa-sdk-clientaa-gateway, aa-sdk-clientlinux, macos, windowscrates_io, github_release, homebrew, install_scriptEvaluatedNot applicable
I2Transport key for the runtime UDS handshakeaa-sdk-clientaa-runtime, aa-sdk-clientlinux, macoscrates_io, github_release, homebrew, install_scriptEvaluatedNot applicable
I3Sub-agent / delegation lineageaa-coreaa-gatewaylinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptEvaluatedNot applicable
I4Process-tree identity across fork/execaa-ebpf-probesaa-ebpf, aa-runtimelinuxcrates_ioUnmeasuredNot applicable
I5Tenant / org isolationaa-gatewayaa-gateway, aa-storage-memory, aa-storage-postgres, aa-storage-sqlitelinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptEvaluatedNot applicable
I6Agent attribution of proxy trafficnoneaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
I7Gateway agent-plane authenticationaa-gatewayaa-gatewaylinux, macos, windowscrates_io, ghcr, github_release, homebrew, install_scriptEvaluatedNot applicable
M1MCP tools/call adjudication by the control planeaa-proxyany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
M10MCP-server governance by configurationaa-devtool-claude-codeclaude_code, copilot, windsurflinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnmeasuredIntegrated
M2MCP enforcement with no gateway configurednoneany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
M3JSON-RPC batch array or malformed envelope carrying tools/callaa-proxyany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
M4Every MCP method other than tools/callnoneany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
M5MCP over stdio (subprocess pipes)noneany_mcp_clientlinux, macos, windowsnot_applicableUnmeasuredNot applicable
M6MCP over SSE (text/event-stream)noneany_mcp_clientlinux, macosnot_applicableUnmeasuredNot applicable
M7MCP over Streamable HTTPaa-proxyany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
M8MCP over WebSocketnoneany_mcp_clientlinux, macosnot_applicableUnsupportedNot applicable
M9MCP on a built-in LLM hostnoneany_mcp_clientlinux, macoscrates_io, github_release, homebrew, install_scriptRedactedNot applicable
N1CONNECT-time egress allow/denyaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
N10Raw TCP that does not speak the proxy protocolnoneanylinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
N11UDP, QUIC, HTTP/3noneanylinux, macos, windowscrates_io, github_release, homebrew, install_scriptUnsupportedNot applicable
N12Local IPC (Unix domain sockets) between third-party processesnoneanylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
N13TLS plaintext observation without the proxyaa-ebpf-probesaa-ebpflinux_aarch64, linux_x86_64crates_ioUnmeasuredNot applicable
N2SSRF guardaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
N3HTTPS payload inspection and credential DLP on the built-in LLM hostsaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptDenied before executionNot applicable
N4HTTPS payload inspection on any other hostaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptEvaluatedNot applicable
N5HTTPS to a host not under MitMaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
N6Model response body scanning on LLM hostsnoneaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
N7Plain http:// requestaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptRedactedNot applicable
N8HTTP/2, gRPC or WebSocket over a MitM’d hostnoneaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnsupportedNot applicable
N9Chunked transfer encodingaa-proxyaa-proxylinux, macoscrates_io, github_release, homebrew, install_scriptUnmeasuredNot applicable
P1Linux x86_64 host-level interceptionaa-proxyaa-ebpf, aa-proxylinux_x86_64crates_ioExperimentalNot applicable
P2Linux aarch64 host-level interceptionaa-proxyaa-ebpf, aa-proxylinux_aarch64crates_ioObservedNot applicable
P3macOS host-level interceptionaa-proxyaa-devtool-claude-code, aa-proxymacoscrates_ioUnsupportedIntegrated
P4Windows mediationnonenot_applicablewindowsnot_applicableUnsupportedNot installed
S1Wrapped framework tool call, deny raised before the tool bodypython-sdkgoogle_adk, langchain_handler, mcp_client_session, microsoft_agent_framework, pydantic_ailinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiDenied before executionNot applicable
S10Direct function call that does not pass a patched seamnoneanylinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiUnmeasuredNot applicable
S11Framework with no adapternoneany_unadaptedlinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiUnmeasuredNot applicable
S12Raw HTTP, subprocess, filesystem, DB driver, browser automation from inside an SDK-adopting processnoneanylinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiUnmeasuredNot applicable
S13The SDK honouring a Deny it receivedaa-sdk-clientaa-sdk-clientlinux, macos, windowscrates_io, github_release, homebrew, install_scriptEvaluatedNot applicable
S2Wrapped framework tool call, deny returned as a sentinel stringpython-sdkagno, crewai, haystack, llamaindex, openai_agents, smolagentslinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiDenied before executionNot applicable
S3Graph / workflow node executionpython-sdklanggraph, mastralinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiObservedNot applicable
S4LangChain tool call via the callback handlernode-sdk@langchain/corelinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiObservedNot applicable
S5LangChain tool call via the explicit wrappernode-sdk@langchain/corelinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiDenied before executionNot applicable
S6Vercel AI SDK / OpenAI Agents tool callnode-sdk@openai/agents, ailinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiEvaluatedNot applicable
S7Node default mode routes every policy check through an allow-all no-op clientnode-sdkall_node_frameworkslinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiUnmeasuredNot applicable
S8Wrapped tool call, Gogo-sdkanylinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiDenied before executionNot applicable
S9Go default build without -tags aa_ffi_go and CGOgo-sdkanylinux, macos, windowscrates_io, ghcr, go_modules, npm, pypiEvaluatedNot 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 advisoryEvaluated, 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, or aa-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 with aasm 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/v1protocol/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

core PyPI npm Go

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 releaseStatusProtocolPython SDKNode SDKGo SDK
v0.0.1-rc.61currentprotocol/v10.0.1-rc.6 (PyPI 0.0.1rc6)npm @rc 0.0.1-rc.6v0.0.1-rc.6
v0.0.1-rc.52supportedprotocol/v10.0.1-rc.5 (PyPI 0.0.1rc5)npm @rc 0.0.1-rc.5v0.0.1-rc.5
v0.0.1-rc.43supportedprotocol/v10.0.1-rc.4 (PyPI 0.0.1rc4)npm @rc 0.0.1-rc.4v0.0.1-rc.4
v0.0.1-rc.34supportedprotocol/v10.0.1-rc.3 (PyPI 0.0.1rc3)npm @rc 0.0.1-rc.3v0.0.1-rc.3
v0.0.1-rc.25supportedprotocol/v10.0.1-rc.2 (PyPI 0.0.1rc2)npm @rc 0.0.1-rc.2v0.0.1-rc.2
v0.0.1-rc.16supportedprotocol/v10.0.1-rc.1 (PyPI 0.0.1rc1)npm @rc 0.0.1-rc.1v0.0.1-rc.1
v0.0.1-beta.47supportedprotocol/v10.0.1-beta.4 (PyPI 0.0.1b4)npm @beta 0.0.1-beta.4v0.0.1-beta.4
v0.0.1-beta.38supportedprotocol/v10.0.1-beta.3 (PyPI 0.0.1b3)npm @beta 0.0.1-beta.3v0.0.1-beta.3
v0.0.1-beta.29supportedprotocol/v10.0.1-beta.2 (PyPI 0.0.1b2)npm @beta 0.0.1-beta.2v0.0.1-beta.2
v0.0.1-beta.110supportedprotocol/v10.0.1-beta.1 (PyPI 0.0.1b1)npm @beta 0.0.1-beta.1v0.0.1-beta.1
v0.0.1-alpha.911supportedprotocol/v1
v0.0.1-alpha.811supportedprotocol/v1
v0.0.1-alpha.711supportedprotocol/v1
v0.0.1-alpha.611supportedprotocol/v1
tested @ 9cf8a033 (post-v0.0.1-alpha.5, unreleased)12supportedprotocol/v1PyPI 0.0.1a5 / git v0.0.2npm @alpha 0.0.1-alpha.5v0.0.1-alpha.4
v0.0.1-alpha.513supportedprotocol/v1
v0.0.1-alpha.414supportedprotocol/v1
v0.0.1-alpha.315supportedprotocol/v1
v0.0.1-alpha.215previousprotocol/v1
v0.0.1-alpha.115previousprotocol/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

SDKRuntime requirementInstallSource
Python SDKPython >=3.12,<4.0Install guidepython-sdk pyproject.toml [project].requires-python
Node SDKNode.js >=18.18.0 (pnpm >=10 to build from source)Install guidenode-sdk package.json [engines]
Go SDKGo >=1.26.0Install guidego-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


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

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

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

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

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

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

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

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

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

  10. 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]).

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

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

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

  14. Published core tag. No SDK tag authoritatively pins this exact commit.

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

LabelMeaning
🟢 PublicSource repository is public on github.com/ai-agent-assembly; anyone can read it.
🔒 Private / internalSource 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:

LabelMeaning
🧪 Release candidateShips 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.
🗺️ PlannedDesigned 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

AreaOwning repositoryVisibilityMaturityWhere to read
Core (gateway, policy engine, eBPF, proxy, FFI, WASM, CLI, API)agent-assembly🟢 Public🧪 Release candidatecore docs
Python SDKpython-sdk🟢 Public🧪 Release candidatepython-sdk docs
Node / TypeScript SDKnode-sdk🟢 Public🧪 Release candidatenode-sdk docs
Go SDKgo-sdk🟢 Public🧪 Release candidatego-sdk docs
Arena (cross-framework governance trials)arena🟢 Public🧪 Release candidatearena docs
Runnable examplesexamples🟢 Public🧪 Release candidaterepo README
Homebrew / install channelhomebrew-tap🟢 Public🧪 Release candidaterepo README
Specs (protocol & policy spec)agent-assembly monorepo🟢 Public🧪 Release candidatePolicy reference · core docs
Releases (versions & compatibility)this hub + each component’s tags🟢 Public🧪 Release candidateCompatibility matrix
Cloud (SaaS control plane)cloud🔒 Private / internal🗺️ PlannedManaged control plane — design preview
Enterprise (SSO, SCIM, advanced audit)agent-assembly-enterprise🔒 Private / internal🗺️ PlannedOpen core boundary
Operations (running & onboarding)this hub🟢 Public🗺️ PlannedManaged SaaS onboarding — design preview

The protocol specification stays in the agent-assembly monorepo by project policy. The reserved agent-assembly-spec repository 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.

SourceWhat it suppliesWhere
ADR 0033 §6 — claim vocabularyThe eleven enforcement/claim terms. Four of the badge names below are §6’s words, reused verbatim.ADR 0033
ADR 0034 — one product truthThe three-axis ruling that decides which vocabulary may describe which subject, and the forbidden designs this page is checked against.ADR 0034
Content-layer ownershipThe L0–L6 layer model and the canonical-owner table.content-ownership.md
Product promise & message hierarchyThe 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/HEAD source 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 form content-ownership.md prefers 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.md owns it, and this page reads it rather than restating it — see area ids.
  • A capability identifier registry. AAASM-5531 has landed (governance/capability-manifest.yaml in agent-assembly), and AAASM-5600 now validates capability_ids against 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 populated capability_ids list 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.

LevelNameAnswersBoundTypical layer
1One-sentence outcomeWhat do I get?Exactly one sentence. Must carry the boundary clause — everything shorter drops it.L0, L1
2Three-step product flowHow does it work, roughly?Exactly three steps, each one short paragraph.L1, L2
3Evaluator detailWhat is on by default, and what does it not cover?No length bound. Must state defaults and non-coverage.L2, L3
4Implementation deep diveHow 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.md carries 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 on protects, enforces and catches, 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_typeMust carryMay also carryReaches level 4 by
product1, 2, 3a deeper link (4 is forbidden here)
guide1, 32a deeper link (4 is forbidden here)
reference3, 41, 2itself
architecture3, 41, 2itself
adr43itself

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:

  1. Every page that does not itself reach level 4 must name where level 4 is, in the deeper metadata 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.
  2. 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/HEAD across repositories in this org; the published docs.agent-assembly.com URL from one rendered site to another.
  3. 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.

BadgeSubject it ranges overDefinition owned byCarried by
available-verifiedThis capability in a published artifactThis pageavailability, platforms[].status
available-with-limitsThis capability in a published artifactThis pageavailability, platforms[].status
previewThis capability in a published artifactThis pageavailability
deprecatedThis capability in a published artifactThis pageavailability
experimentalOne action, on evidenceADR 0033 §6, verbatimclaims[].term only
plannedOne action, on evidenceADR 0033 §6, verbatimclaims[].term only
unsupportedThis capability on one channel + platformADR 0033 §6, verbatimplatforms[].status only
unmeasuredOne action, on evidenceADR 0033 §6, verbatimclaims[].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:

  • experimental and planned are claim terms, so they live in claims[]. They are not values of any key this page owns. A capability that is decided but not implemented is recorded as claims: [{term: Planned, evidence: <ticket>}] — with no availability value at all, because a planned capability is in no artifact.
  • unsupported is per (channel, platform), never page-level. §6 names “the platform matrix row” as its required evidence, so the term belongs in a platforms[] 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.
  • unmeasured is 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 except claims[].term. A distribution fact that was never checked is not a badge at all — it is a missing platforms[] 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.

BadgeMeansRequired evidence
available-verifiedPresent in every published artifact named by a platforms[] row, at the named version, checked against a published tagA platforms[].evidence string per shipping row, plus last_verified
available-with-limitsPresent, but a stated limit changes what a reader may rely onThe above, plus a non-empty limitations
previewPresent, but outside the compatibility commitment — it may change without a deprecation cycleThe above
deprecatedPresent, and scheduled for removalThe above, plus a limitations naming the replacement

available-verified is 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 in claims[]. Writing available-verified and expecting a reader to infer protection is the promotion error content-ownership.md lists 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:

AxisVocabularyOwnerRanges over
Behaviour on evidenceADR 0033 §6’s eleven claim termsADR 0033 §6 (Core)One action on one host, at one time
Documentation-area maturity🧪 Release candidate, 🗺️ PlannedDocs Hub source-of-truth.mdOne area of Agent Assembly documentation
Portfolio lifecycleavailable, beta, release_candidate, coming_soonThe company site’s pinned product registryOne 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:

  1. No maturity key. A page does not restate its area’s maturity. Documentation- area maturity ranges over an area, not a page, and source-of-truth.md owns it — so it is read from the area row, via this page’s area key, 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.
  2. No §6 term as a completeness value. experimental and planned are claim terms; carrying them under a key named for completeness is forbidden design 12’s second clause exactly. They are in claims[].
  3. availability coins 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>
BadgeClass suffixTone
available-verified--available-verifiedpositive
available-with-limits--available-with-limitscaution
preview--previewcaution
experimental--experimentalcaution
planned--plannedneutral
deprecated--deprecatedcaution
unsupported--unsupportednegative
unmeasured--unmeasuredneutral

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 equals END 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-META occurs 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:

OutcomeMeaningEffect
errorThe page is invalidBuild/CI fails
warningThe page is valid but stale or degradingReported, does not fail

Field reference

R = required, O = optional, C = conditional (see cross-field rules).

KeyTypeReqAllowed valuesMissing / invalid
schema_versionintegerR1error
page_typestringRproduct · guide · reference · architecture · adrerror
audiencelist of string, non-emptyRevaluator · developer · operator · security-engineer · contributor · auditorerror
user_jobstringR10–120 chars; one sentence — no interior period-then-space, no trailing perioderror
ownerstringRL<n>:<surface>, exactly as paired in the surface tableerror
canonical_sourcestringRself, or a link in the canonical-link formerror
describes_capabilitybooleanRtrue · falseerror
areastringCone of the 12 area idserror if required and absent
availabilitystringCavailable-verified · available-with-limits · preview · deprecatederror
platformslist of objectCsee platforms[]error
last_verifiedobjectCsee last_verifiederror
claimslist of objectCsee claims[]error
limitationsstringCnon-empty; a link or an in-page anchorerror
disclosure_levelslist of integer, non-emptyRsubset of [1,2,3,4], ascending, no duplicateserror
deeperstringCa link in the canonical-link formerror
capability_idslist of stringOeach 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 offerror

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:

ownerLayerSurface
L0:horonomy.devL0 Company sitehoronomy.dev
L1:official-websiteL1 Product websiteofficial-website
L2:docsL2 Docs Hubdocs
L3:agent-assemblyL3 Component docsagent-assembly (Core)
L3:python-sdkL3 Component docspython-sdk
L3:node-sdkL3 Component docsnode-sdk
L3:go-sdkL3 Component docsgo-sdk
L3:arenaL3 Component docsarena
L3:cloudL3 Component docs (private)cloud
L3:agent-assembly-enterpriseL3 Component docs (private)agent-assembly-enterprise
L4:examplesL4 Examplesexamples

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:

  • cloud and agent-assembly-enterprise are 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 is L2:docs; L3:cloud names 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.md states that nothing in L6 is a reader-facing page. Neither owns a page’s content, so no pair exists for them, and owner accepts no L5: or L6: 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 docs repo but a page here that summarises a Core fact carries L3:agent-assembly and a canonical_source link, per rule 9.
  • canonical_source: self is available only to L2:docs and the L3: surfaces. Rule 9 turns on the owner surface naming the repository the page is in, and only those nine surfaces are repository names. L0:horonomy.dev is a domain — its repository is horonomy-official-website, in a different organisation — and L1:official-website and L4:examples name 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 idRow 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 id per area in hub-components.toml and 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 because hub-components.toml and 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 from released_matrix and this page should say so rather than keeping a second hand-maintained copy — that is the same hand-off capability_ids already carries. Recording the correspondence now is what stops a third spelling appearing later.

Note also that availability answers 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.

KeyTypeReqAllowed values
channelstringRgithub-release · homebrew · ghcr · install-sh · crates-io
platformstringRlinux-x86_64 · linux-aarch64 · macos · windows
statusstringRavailable-verified · available-with-limits · unsupported
evidencestringCnon-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: true must therefore enumerate a row for every channel in the enum, using unsupported where it does not ship. Partial enumeration is an error — except where rule 4 applies, in which case platforms is exactly [] and no row is written at all.
  • unsupported requires no evidence string 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_64 asserts nothing about github-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

KeyTypeReqAllowed values
versionstringRa release version, e.g. v0.0.1-rc.6
refstringRa tag matching ^v\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?$, or a 40-character hex SHA
datestringRISO 8601 YYYY-MM-DD
methodstringRnon-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, master and HEAD are therefore hard errors in ref, 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 a platforms[] row you cannot yet fill — not a ref that overstates.

Freshness:

ConditionOutcome
date more than 180 days olderror — evidence is stale
date more than 90 days oldwarning
version differs from the current releasewarning
date in the futureerror

“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:

KeyTypeReqAllowed values
termstringRone of ADR 0033 §6’s eleven terms, verbatim
evidencestringRnon-empty — a link, or an E-block reference into the public claim inventory
subjectstringOself (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.

#RuleOutcome if violated
1describes_capability: truearea, platforms, last_verified and claims all presenterror
2describes_capability: falsearea, availability, platforms, last_verified, claims, limitations all absenterror
3availability: available-with-limits or deprecatedlimitations present and non-emptyerror
4claims[] contains a Planned entry with subject: self (or no subject) ⇒ availability absent, platforms exactly [], and claims contains no other self-subject entryerror
5availability: available-verified ⇒ every platforms[] row has status ∈ {available-verified, unsupported} — no row may be available-with-limitserror
6availability is present iff describes_capability: true and claims[] does not contain a self-subject Plannederror
7platforms[].status may never be preview or deprecated, and never a §6 term other than unsupportederror
8claims[].term may never be a value outside §6’s elevenerror
9canonical_source: self ⇒ the owner surface names the repository the page is in; otherwise canonical_source must be a linkerror
10canonical_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 rejectederror
11max(disclosure_levels) < 4deeper presenterror
12disclosure_levels must be a subset of the page type’s must ∪ may levels, and include all of its must levels — see the tableerror
13An unbounded claim verb appears in the body ⇒ describes_capability: true, claims non-empty, and limitations presenterror
14claims[] contains any of Observed, Detected, Evaluated, Denied before execution, Redacted, Approval required, Degradedlimitations present and non-emptyerror
15page_type is product or guide4 ∉ 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 operations page documenting a capability that genuinely ships would have been forced to claim Planned, which rule 4 then forces to platforms: [] — asserting the capability is in no published artifact, which is false, and which this page elsewhere calls a validation error.
  • A core page documenting a genuinely planned capability — the Windows host adapter, Unsupported in ADR 0033 §5.3 — could not have used §6’s Planned at 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 termWhy
Unmeasured§6: no control inspected the action; there is no capability to bound
Unsupported§6: not available on this platform/configuration
PlannedDecided but not implemented, and §6 attaches no capability claim; rules 4 and 6 already force platforms: [] and forbid availability
ExperimentalImplemented but not validated for production — §6 requires the missing validation be named, which is itself the bound

Observed and Detected are in the rule, not excluded from it. An earlier draft excluded them as reporting “an absence of control”, which is simply wrong: §6 defines Observed as an event reached the evidence pipeline and Detected as 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 to Detected with an explicit not Denied before execution caveat. A page claiming Detected with 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:

  1. Fenced code blocks.
  2. Inline code spans.
  3. 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, ensures and the bare infinitives — was tested against this page and rejected: blocks alone 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 promiseif 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_versionDateChangeMigration
12026-08-06Initial definition.

What this page hands off

ToWhat
AAASM-5601Implement 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-5610Apply 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 0034Precedence 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-5600The 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.

SourceWhat it suppliesWhere
Page standardsThe audience enum this page’s audiences are, the four disclosure levels, and the metadata contract every page named below must satisfypage-standards.md
Content-layer ownershipThe L0–L6 layer model, each layer’s primary audience, and the rule that a derivative may narrow but never widencontent-ownership.md
ADR 0034 — one product truthThe T1–T7 authority hierarchy, hand-off 7’s three-axis ruling, and the reviewer classes that sign off a boundaryADR 0034
Product promise & message hierarchyThe one approved promise, the default-posture table, and the Provisional listproduct-promise.md
Risk scenariosThe flagship story and three supporting threats, with the Tier 1 / Tier 2 publication gaterisk-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.md or a manifest row. Nothing here is a new capability statement, which is why this page carries describes_capability: false.
  • Page metadata rules. page-standards.md owns the block, the field reference and the fifteen cross-field rules. This page consumes its audience key; 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, a developer or an evaluator as 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-engineer and developer, 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: audience is 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 Unmeasured and 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 scopeaudience valueNote
Executive / EvaluatorevaluatorJob EV1
PM / Engineering LeaderevaluatorJob EV3 — the same value, a different job
Security / Risksecurity-engineerIncludes L3’s security researchers
QA / AssuranceauditorL6’s audience in the layer table
Platform / SREoperator
Application / AI Developerdeveloper
Maintainer / Contributorcontributor

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.

FieldWhat it answersWhy an IA needs it
WhoWhich real roles this value coversStops two readers being served one page by accident
Arrives knowingThe context the reader already hasFixes the disclosure level an entry page opens at
Must be able to doThe capability the reader leaves withThe test a candidate page is judged against
JobsEach job, and the decision or action it ends inThe unit a page is designed around
Never hiddenWhat must be reachable from their route, without exceptionThe content a navigation redesign may not bury
Belongs elsewhereContent this reader is not the audience forThe anti-persona, stated as content rather than as a person
Entry · next · escalationThe three-step routeThe 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 domainS 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 T1T3 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-gatewayG8, 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.

JobStated as a jobEnds in
EV1Decide whether this product is worth a trialA trial started, or a recorded reason not to
EV2Decide whether one stated capability meets one stated requirementMet · met with a named limit · not met
EV3Decide what to sequence, and what to tell a stakeholder is not available yetA 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.

JobStated as a jobEnds in
SE1Decide whether this boundary is acceptable for a named class of agent trafficAn approval or a refusal for one deployment
SE2Determine what a quiet result meansAn uninspected action read as Unmeasured rather than as clean
SE3Report a vulnerabilityA 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.mdwhich 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.

JobStated as a jobEnds in
AU1Find the evidence behind one published claimA cited row, or a recorded gap
AU2Verify an audit recordA result read as integrity of the entries present, not completeness of the log
AU3Decide whether a demonstration may be published as evidenceTier 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.

JobStated as a jobEnds in
OP1Route an agent through the product on one hostA governed launch
OP2Decide what to install, on which platform, from which channelAn install performed
OP3Work out why a control did not fireA corrected configuration, or an accepted bound
OP4Stand up a limited-function self-hosted stack for evaluationA 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.

JobStated as a jobEnds in
DV1Add a policy checkpoint to an agent in my languageCode that runs and reaches a decision
DV2Choose an SDK modeAn explicit choice between the advisory default and the check-capable mode
DV3Find a runnable integration for my frameworkAn 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.

JobStated as a jobEnds in
CO1Decide where a fact belongs before writing itA layer and a canonical owner named in the ticket
CO2Write a page that conformsA page carrying a valid metadata block
CO3Route a correctionA pull request against the canonical source first
CO4Decide whether a change is a material truth changeThe 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 audienceWhyWhere such a reader goes
Someone evaluating the company rather than the productL0’s job, and L0 must not carry a per-capability status or a platform claimhoronomy.dev
A reader looking for agent-building guidanceThis product governs an agent; it does not help write one. A page that teaches agent construction has widened the product’s subjectFramework documentation
A procurement or compliance reader wanting an SLA, a region or a certificationPlanned, not available; asserting any of it is a managed-service claim bounded by the checklistsaas-claim-publication-checklist.md and source-of-truth.md
A reader of the private cloud or agent-assembly-enterprise internalsOutside the public content boundary; paraphrasing does not make it publishableThe 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

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-EV1-aEV1The one approved promise, with its boundary clause on the same screenproduct-promise.md◐ partial (GAP-1, GAP-8) — filed under About, and no evaluator route reaches it
IR-EV1-bEV1One concrete story of a decision the product made, with its determinationrisk-scenarios.md✅ satisfied
IR-EV1-cEV1What is on by default, as a table rather than as proseproduct-promise.md level 3◐ partial (GAP-1, GAP-8) — one satisfier, and it is not on the route
IR-EV2-aEV2Per capability: whether it ships, on which channel and which platformmanifest released_channels / released_platforms (L6)✗ gap GAP-3, GAP-4
IR-EV2-bEV2Per capability: whether anything reaches it by defaultmanifest default_state (L6); product-promise.md for the headline set◐ partial
IR-EV2-cEV2The stated limit that changes what may be relied onproduct-promise.md, risk-scenarios.md known-boundary blocks✅ satisfied
IR-EV3-aEV3The maturity of each documented areasource-of-truth.md✅ satisfied
IR-EV3-bEV3What is decided but not built, with its ticket and no capability claimscattered Planned statements; no roadmap surface✗ gap GAP-6
IR-EV3-cEV3The open-source / managed split, so a plan can be split along itopen-core-boundary.md✅ satisfied

Requirements for security-engineer

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-SE1-aSE1The trust boundary, in the current architecture rather than a superseded onecore ADR 0033 and the core security section◐ partial — the hub entry page is superseded (GAP-5)
IR-SE1-bSE1The enumerated bypasses, published rather than impliedrisk-scenarios.md; manifest known_bypasses◐ partial (GAP-4) — complete only at L6
IR-SE1-cSE1The failure posture per control, including the fail-open onesmanifest failure_posture; G9 in risk-scenarios.md◐ partial — one worked instance published, the rest at L6 (GAP-4)
IR-SE1-dSE1The platform matrix, stated in both directionscore 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-aSE2That an uninspected action is reported as Unmeasured, never as cleanproduct-promise.md, risk-scenarios.md✅ satisfied
IR-SE2-bSE2What a passing chain verification does and does not establishproduct-promise.md✅ satisfied
IR-SE3-aSE3The vulnerability reporting address for the repository in questioneach 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

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-AU1-aAU1A published route from a claim to the row or record that backs itnothing published; the manifest is L6✗ gap GAP-4
IR-AU1-bAU1Which claims are Provisional, and the ticket that would close eachproduct-promise.md✅ satisfied
IR-AU2-aAU2What the verification command establishes, and its two negative resultsproduct-promise.md✅ satisfied
IR-AU3-aAU3The Tier 1 / Tier 2 gate, and which tickets lift itrisk-scenarios.md✅ satisfied
IR-AU3-bAU3For a managed-service claim, the register that bounds itsaas-claim-publication-checklist.md◐ partial — interim, and managed-service only

Requirements for operator

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-OP1-aOP1That routing is performed per agent and per launchproduct-promise.md level 2 step 1◐ partial (GAP-1) — not on the operator route
IR-OP1-bOP1The launch preconditions in full, none droppedcore quick-start; risk-scenarios.md governed-path fields◐ partial
IR-OP2-aOP2Which artifact reaches which platform, from which channelmanifest (L6); compatibility.md carries versions only✗ gap GAP-3
IR-OP2-bOP2An install route that does not begin with an unavailable pagedocker-containers.md✗ gap GAP-2 on the entry route
IR-OP3-aOP3Which controls are off until configuredproduct-promise.md level 3◐ partial (GAP-8)
IR-OP3-bOP3The failure postures, including the ones that produce no decision-path signalrisk-scenarios.md for G9; manifest for the rest◐ partial (GAP-4)
IR-OP4-aOP4The scope of a limited-function self-host, and what it excludesopen-core-boundary.md, docker-containers.md✅ satisfied
IR-OP4-bOP4What the stack records, and how to read itself-host-observability.md◐ partial (GAP-2) — filed off the entry route

Requirements for developer

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-DV1-aDV1A language-specific first checkpoint, reachable from this hubthe SDK docs, mounted and listedREADME.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-bDV1The initialisation preconditions the wrapper depends onSDK docs; manifest S1 preconditions (AA_AGENT_ID set)◐ partial
IR-DV2-aDV2That the SDK is advisory, and what the check-capable mode changesproduct-promise.md level 3◐ partial (GAP-1, GAP-8) — not on a developer route
IR-DV2-bDV2What sits outside the wrapper, stated as preconditions on their codemanifest S1 known_bypasses, and rows S10S12 (L6)✗ gap GAP-4
IR-DV3-aDV3A runnable integration per framework, and a way to choose between themthe 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

IDJobWhat a page must supplyWhere it is satisfied todayStatus
IR-CO1-aCO1The canonical owner per content typecontent-ownership.md✅ satisfied
IR-CO1-bCO1The four sanctioned reuse patterns, and when each appliessame✅ satisfied
IR-CO2-aCO2The metadata contract and a copyable template per page typepage-standards.md✅ satisfied
IR-CO2-bCO2Which audience and job a page is being written forthis page✅ satisfied
IR-CO3-aCO3Where a correction goes first, as an ordered procedurecontent-ownership.md✅ satisfied
IR-CO4-aCO4The reviewer classes, and what counts as a material truth changeADR 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.

#GapRequirements it blocksPage-shape implied
GAP-1No 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 themIR-EV1-a, IR-EV1-c, IR-SE1-d, IR-SE3-a, IR-OP1-a, IR-DV1-a, IR-DV2-aA per-audience entry for the six, on both L1 and L2 — extending the index’s existing role paragraph, not replacing it
GAP-2The 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 rowIR-OP2-b, IR-OP4-bAn operator entry that starts from what ships
GAP-3No 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_platformsIR-EV2-a, IR-OP2-aA 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-4The 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 yetIR-AU1-a, IR-DV2-b, and the L6-only half of IR-EV2-a, IR-SE1-b, IR-SE1-c, IR-OP3-bA generated claim-to-evidence surface at L2
GAP-5The 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 noteIR-SE1-aA security entry rewritten against ADR 0033, not a note added to the old one
GAP-6No 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 surfaceIR-EV3-bA bounded forward-looking page at L1, in the admissible forms only
GAP-7The 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 checkpointIR-DV1-aA sidebar route to the mounts; the checkpoint itself belongs in the SDK docs at L3, not here — see the note below
GAP-8The 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 todayIR-EV1-a, IR-EV1-c, IR-OP3-a, IR-DV2-aA 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_channels vocabulary does not contain. docker-containers.md states that five multi-arch images are published to ghcr.io/ai-agent-assembly; the manifest’s released_channels values across all eighty rows are crates_io, github_release, homebrew, install_script, pypi, npm and go_modules — eight distinct values counting the sentinel not_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.md also carries the superseded model. product-promise.md names two pages still carrying the older framing — security-model.md and glossary.md. GAP-5 is scoped to the security entry page because that is the one on a security-engineer route, but a sitemap treating GAP-5 as 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.yaml is present in the core repository under AAASM-5531 with a schema, a semantic validator and a CI gate. That affects the reserved status of capability_ids and the platforms[] hand-off, both of which that page marks as pending AAASM-5531. It is page-standards.md’s edit to make, at whatever schema version it decides.

  • The evaluator value cannot separate EV1 from EV3. Recorded above; the evidence is handed to page-standards.md rather 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.

  1. 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’s audience and user_job keys ask, so a conforming page has already answered it.
  2. 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.
  3. 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.
  4. 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

  1. Every audience has an entry. Six values, six routes. A value with no entry means a reader with no way in.
  2. Every route reaches its escalation in three steps. Entry, next, escalation. A fourth step is a route nobody finishes.
  3. No route opens on a 🗺️ Planned page. Gap GAP-2 is what that looks like when it happens.
  4. 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.
  5. No route requires a reader to reach L6 to finish their job. Where it does today, that is gap GAP-4 and the fix is a surface, not a deep link.

How this page meets its acceptance criteria

CriterionHow it is met
Every audience has an explicit job-to-be-done and information contractSix 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 maintainerssecurity-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 navigationUsing 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 boundariesNot 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

ToWhat
AAASM-5594The 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-5587The 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-5611The 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.mdTwo records: that the evaluator value cannot separate EV1 from EV3, and that its AAASM-5531 pending note is now stale
AAASM-5680That 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.

SourceWhat 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 §6The eleven claim terms. Nothing here coins a twelfth. ADR 0033
ADR 0034The 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.1The protection-state ladder, and which rung may carry a bypass-resistance claim. ADR 0030
Claim vocabularyThe 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:

  1. A brief may be silent. An engineer does not need the tenant-isolation posture, and omitting it is correct. Silence is not a disagreement.
  2. 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 coverage field.

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 Observed and Evaluated over rows carrying unmeasured, and the tell was that the level-4 mapping recorded unmeasured for the same two rows — one page, two tables, two answers. Comparing the term column against coverage catches that without reading a word of prose.

#§6 termClaim, in the wording a role page usesBound that travels with itManifest rows
RC1Denied before executionA connection made on a path you routed through Agent Assembly is checked against the destination list you configured and refused before the proxy dials itThe 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 madeN1
RC2Denied before executionRequests to loopback, private, link-local and related address space are refused, including where a public hostname resolves into themOn 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 soN2
RC3RedactedOn the model-provider hosts Agent Assembly inspects, a recognised credential is removed from the request before it is forwardedThree 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 scannedN3, C1, C4, C6, G4
RC4Denied before executionAn MCP tool call can be checked against your policy by the control plane and refused before the proxy forwards itThe 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 uncoveredM1, M3; exclusions M2, M4M9
RC5Denied before execution (Python, Go) · Evaluated (Node) · Unmeasured (Node default mode)A tool call through a wrapped framework seam is checked before the tool body runsThe 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 allowedS1, S2, S5, S6, S7, S8, S9, S13, G5
RC6UnmeasuredWhether 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 oneThe 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 gapsG10
RC7UnmeasuredWhere nothing inspected an action, the record says nothing was inspected — not that it was allowedScoped 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 behaviourN5, N10, N12, S10, S11, S12, L6, H1, H6, H7
RC8Observed · DetectedOn Linux, kernel probes report TLS plaintext, process execution and file activityNo 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 scriptH2, 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 rungADR 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 enforcementL1; P3 for the demotion
RC10DegradedWhere a control was planned and is unavailable, the product reports the planned level and the level actually achievedDegraded 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 allG6; reporting gap G11; silent case G7
RC11Denied before execution · EvaluatedWhere the control plane is configured and becomes unreachable, the decision path refuses rather than allowingFail-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 openG1, G3, G8; the inverse G2
RC12Approval requiredNo 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)
RC13UnmeasuredWhether a declared spend cap is checked in the decision path is not established by any manifest rowSame 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 gapsG9
RC14UnsupportedNamed transports and platforms are not available, and the matrix says whichWindows 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 productP4, N8, N11, M8
RC15Denied 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 pathWriting 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 detectableL1, L2, L3, L4, L5, L7, L8, H8, M10
RC16EvaluatedAn agent registers with an Ed25519 did:key identity and a possession proof, and delegation lineage is derived server-sideThe 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 authenticatedI1, 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.

SubjectWhat exists in the manifestWhat is missing
The evidence pipeline — a decision durably reaching the audit chainG10, “Audit emission failure”, domain: degraded_mode, coverage: unmeasured, evidence: gapA 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 pathG9, “Budget state unreadable or corrupt”, domain: degraded_mode, coverage: unmeasured, evidence: gapA 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.

FieldWhat it answersWhy the field exists
PainWhat is going wrong for this role todayThe reader recognises themselves or leaves
TriggerThe event that makes it this role’s problem this quarterDistinguishes a concern from a priority
InterventionWhat Agent Assembly does about it — register entries onlyThe one field where a new claim could enter, and the register is why it cannot
OutcomeWhat is different afterwards, stated as a decision, not an averted consequencePrevented-outcome wording is gated behind AAASM-5532 / AAASM-5529
ProofWhat the reader can check, and whereA role page that asserts without a route to evidence is a brochure
LimitationsWhat this role will discover later if the page does not say it nowThe field that decides whether the page survives contact with an evaluator
NextOne next page and one next actionTwo 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.

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 — including GatewayProtected — 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 required is 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.

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_only defaults 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_tool and coverage fields of the S rows 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 required is 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 evidence and evidence_runs_on_main fields, 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 Unmeasured because 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)SecurityPlatformEngineeringProduct/QA
RC1Denied before execution·
RC2Denied before execution···
RC3Redacted
RC4Denied before execution·
RC5Denied before execution (Python, Go) · Evaluated (Node) · Unmeasured (Node default mode)···
RC6Unmeasured·
RC7Unmeasured···
RC8Observed · Detected··
RC9— (ADR 0030 protection state)·
RC10Degraded···
RC11Denied before execution · Evaluated··
RC12Approval required·
RC13Unmeasured···
RC14Unsupported·
RC15Denied before execution (via RC1)·
RC16Evaluated···

Three properties of this table are the acceptance criteria, and each is checkable without reading the prose:

  1. 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.
  2. 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.
  3. 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 bypassed or unbypassable. 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 fleet or whole fleet. Coverage is a per-agent, per-launch, per-platform fact.
  • Not universal, comprehensive or complete attached 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, prevents or guarantees used 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 -> eBPF pipeline 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>, an og:title or 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.

#RowsCoverage term(s) in those rowsDecision timingDefault stateFailure postureReachability note
RC1N1denied_before_executionpreopen (lists empty)fail_openshipped_with_platform_exception; macOS crates.io only
RC2N2denied_before_executionpreonfail_closedas N1
RC3N3, C1, C4, C6, G4denied_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
RC4M1, M3denied_before_executionpreoff (M1) / on (M3)fail_closedas N1; M3 evidence is unit_only
RC5S1, S2, S5, S6, S7, S8, S9, S13, G5denied_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
RC6G10unmeasuredpostopenfail_openshipped; AAASM-5626
RC7N5, N10, N12, S10, S11, S12, L6, H1, H6, H7unmeasurednonenot_applicable / on (N5)not_applicableH1, H6, H7 are absent_mechanism
RC8H2, H3, H4, N13, I4, P1, P2detected (H2), observed (H4, P2), experimental (P1), unmeasured (H3, N13, I4)postoff (H2, P1) / on (rest)fail_openshipped_crates_io_only — the loader daemon is unreleased
RC9L1; P3denied_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
RC10G6; G11; G7degraded (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
RC11G1, G3, G8; G2denied_before_execution (G1, G3), evaluated (G8), unmeasured (G2)preclosed (G1, G3, G8) / open (G2)fail_closed; G2 is fail_openshipped
RC12(none)No row reaches approval_required
RC13G9unmeasuredpreopenfail_open_silentshipped
RC14P4, N8, N11, M8unsupportednonenot_applicablenot_applicableP4, M8 are absent_mechanism
RC15L1, L2, L3, L4, L5, L7, L8, H8, M10denied_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
RC16I1, I2, I3, I5, I6, I7evaluated (I1, I2, I3, I5, I7), unmeasured (I6)pre (I1I3, I5, I7) / none (I6)on (I1I3) / off (I5) / open (I7) / not_applicable (I6)fail_closed (I1I3) / 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

QuestionOwner
Implementing the four role surfaces, their routes, navigation and metadataAAASM-5587
The sitemap the role surfaces sit inAAASM-5594
The homepage, the Product page and the How It Works pageAAASM-5585, AAASM-5586
The audience enum and the role crosswalk this page aligns toAAASM-5591
Adding capability rows for the evidence pipeline and for budget enforcement, so RC6 and RC13 can move off UnmeasuredAAASM-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 keepAAASM-5531
Lifting the Tier 2 gate on prevented-outcome wordingAAASM-5532, AAASM-5529
Making Approval required claimable — a shipped operator surface for a held actionAAASM-5657
Rendering a degradation event anywhere a user can see itAAASM-5535
Comprehension, accessibility and truthful-wording validation of the built pagesAAASM-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.

SourceWhat 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 §6The eleven claim terms, and forbidden designs 1 and 2. ADR 0033
ADR 0034 §1The T1–T7 truth hierarchy, and hand-off 4 assigning the roadmap to L1. ADR 0034
source-of-truth.mdThe 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.md owns the role copy, product-promise.md owns 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, and documentation-inventory.md marks the hub page Review, 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.md owns 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.md gains a six-row router — one row per audience value, naming that reader’s entry, next step and escalation. GAP-1 asks for exactly this and bounds it: the index already carries a role paragraph covering operator and security-engineer and routing developer and contributor off-site, so the router extends that paragraph rather than replacing it, and the residue is evaluator and auditor.

The Docs Hub sitemap

The six sections, and the task each one is

SectionThe task or decision a reader arrives withaudience values entering hereJobs
EvaluateDecide whether to adopt this, and learn what it leaves uncoveredevaluator, security-engineerEV1, EV2, EV3, SE1
IntegrateAdd a policy checkpoint to an agent I am buildingdeveloperDV1, DV2, DV3
OperateRun it on a host, and work out why a control did not fireoperatorOP1, OP2, OP3, OP4
VerifyCheck a published statement against the evidence behind itauditor, security-engineerAU1, AU2, AU3, SE2, SE3
ReferenceLook up one exact field, value or versionall sixsupports every job; ends none
ContributeDecide where a fact belongs, and write a page that conformscontributorCO1, 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 exceptionpolicy-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.

PagePurposeCanonical owner
README.mdThe index, and the six-row audience routerL2:docs
documentation.mdThe component router — the standing route to /core/, /python-sdk/, /node-sdk/, /go-sdk/ and /arena/L2:docs

Evaluate — 8 existing pages

PageWhy it is hereNote
product-promise.mdLevels 1–3 of the promise, including the default-posture tableCloses GAP-8 on the evaluator route only — one of the three routes that gap blocks. Opens the section until AAASM-5609 lands
risk-scenarios.mdThe flagship story and three supporting threats — IR-EV1-bHome section; Verify links to its negative-control section rather than re-filing it
security-model.mdThe security-engineer entry — SE1Carries a superseded model (GAP-5); the rewrite is another ticket’s
comparison.mdCategory placement — EV1
open-core-boundary.mdThe open-source / commercial split — IR-EV3-cCanonical owner of that split per the ownership table
faq.mdFirst-visit questions, before any detailed page
quickstart-saas.mdManaged-service evaluation — EV3🗺️ Planned; filed last, never in Operate
cloud-deployment.mdManaged-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

PageWhy it is here
docker-containers.mdOP2, OP4 — what ships as an image and how the topology is wired
self-host-observability.mdOP4 — what the running stack exposes
troubleshooting.mdOP3 — why a control did not fire

Verify — 1 existing page, 1 new

PageWhy it is here
saas-claim-publication-checklist.mdAU3 — the interim T3 register bounding managed-service claims

Reference — 4 existing pages

PageWhy it is hereNote
policy-reference.mdField-by-field lookupReview — a second reference for content Core owns; AAASM-5586 / AAASM-5609
glossary.mdTerm lookupCarries the same superseded framing as security-model.md; AAASM-5658
compatibility.mdVersion pairing across componentsGenerated from compatibility.toml
source-of-truth.mdThe status map — the only place an area’s maturity is setGenerated from hub-components.toml

Contribute — 6 existing pages, plus this one

PageWhy it is here
page-standards.mdCO2 — the metadata contract
audiences.mdCO1 — who a page is for
role-narratives.mdThe role copy contract
sitemaps.md (this page)Where a page goes
docs-hub-aggregation.mdHow the hub is assembled
accessibility.mdSite policy
localization.mdSite 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 pageSectionWhat it is bounded toOwner
integrate.mdIntegrateA language chooser and a pointer to the SDK mode decision. It may not restate an install step, an API surface or a mechanismAAASM-5608
operate.mdOperateA router: route an agent → install for this platform → observe → diagnose. It may not restate a mechanismAAASM-5608
verify.mdVerifyHow 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 TodayEvaluateThe current capability and status answerAAASM-5609
Choose Your Enforcement PathEvaluateThe path decision across runtime checkpoints, transport mediation and host adaptersAAASM-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:

ShapeBuildResult
External URL entryexit 101failed to write src/https://…
Duplicate entry for a page already listedexit 101Duplicate file in SUMMARY.md
Draft entry - [Python SDK]()exit 0Renders a <span> with no anchor — a label, not a route

That leaves two real options, and neither is free:

  1. Move documentation.md out 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.
  2. A theme-level navigation block, outside SUMMARY.md. The hub already injects theme/head.hbs and four additional-js files, so the mechanism exists. This is the only way to put the literal mount names in the sidebar, and therefore the only way to close GAP-7’s sidebar half as audiences.md words 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.

RouteWhere it sitsOutside a task section?Reaches
documentation.md, a prefix chapterAbove the six sections, every page, every viewportYesAll five mounts
README.md’s SDKs & components tableThe index, also a prefix chapterYesAll five mounts, plus standalone per-version sites
source-of-truth.md’s status mapReferenceNoAll 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:

  1. Placement. quickstart-saas.md and cloud-deployment.md sit last in Evaluate. They are not in Operate, and they are not the first chapter of any section. Their reader today is EV3 — deciding what to tell a stakeholder is not available yet — not an operator.
  2. 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.
  3. The promotion gate. A managed-service page moves from Evaluate into Operate when, and only when, its area’s Maturity cell in source-of-truth.md’s BEGIN GENERATED:hub-components:source-of-truth-table region 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

RouteThe task or decisionStatusOwner
/Decide in five seconds whether this is relevant to meExists — rewriteAAASM-5585
/productUnderstand what it is before choosing a pathExists — rewriteAAASM-5586
/how-it-worksUnderstand the flow without source-level knowledgeNewAAASM-5586
/use-cases and four childrenRecognise my own situation in a concrete storyNewUnassigned
Four role routes under one prefixDecide relevance for my role in under three minutesNewAAASM-5587
/trustGet from a published claim to the evidence behind itNewUnassigned
/maturityLearn what ships today, and what is decided but not builtNewUnassigned
/blog, /blog/tags/*Read build notesExists — keep
/early-accessRegister interest in the managed serviceExists — keep
/arenaSee governance trialsExists — 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.

BriefaudienceJob it ends — this page’s derivation, not a cited mapping
Security / Risksecurity-engineerSE1
Platform / SREoperatorOP1OP3 framing
EngineeringdeveloperDV2
Product / QA / AssuranceauditorAU3

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.

audienceL1 entryL2 entry
evaluator/, and /maturity for the forward-looking halfEvaluate
security-engineerSecurity / Risk role routeEvaluate — security-model.md
operatorPlatform / SRE role routeOperate
developerEngineering role routeIntegrate
auditorProduct / QA role routeVerify
contributorNone, by designContribute

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. /trust routes 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-works most 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>, an og:title or a social card — those take product-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 stateAxis that already carries itValueSet inRendered as
CurrentPage availabilityavailable-verifiedThe page’s own metadata blockA page badge
LimitedPage availabilityavailable-with-limitsThe page’s own metadata blockA page badge
ExperimentalPage availabilitypreviewThe page’s own metadata blockA page badge
DeprecatedPage availabilitydeprecatedThe page’s own metadata blockA page badge
PlannedDocumentation-area maturity🗺️ Plannedsource-of-truth.md, reached through the page’s area idAn 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.md deliberately has no maturity key for exactly this reason. A page is 🗺️ Planned because its area is.
  • Experimental and Planned are 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 not Planned in §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 🗺️ Planned reaches 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 todayaudiences.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 surfaceLayer that owns itWhere it landsWhy
Developer Quickstart — PythonL3 python-sdk/python-sdk/, routed from IntegrateIntegration steps, per language is that SDK’s in the ownership table
Developer Quickstart — NodeL3 node-sdk/node-sdk/, routed from Integrateas above
Developer Quickstart — GoL3 go-sdk/go-sdk/, routed from Integrateas above
Operator QuickstartL3 Core/core/, routed from OperateIntegration steps, operator / CLI path is Core’s
End-to-end governance walkthroughL2, this hubOperateIt 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 by page-standards.md, which states that a hub page about the managed service is L2:docs while L3:cloud names 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:

StepResult
Build the hub unchanged, list every emitted .html27 files
Move product-promise.md and risk-scenarios.md into a new # Evaluate part; rebuildexit 0
diff the two file listsidentical — 0 URL changes
Control: does the sidebar actually change? Evaluate in the generated toc-*.js0 in base, 1 in mutant
Control: is the probe capable of finding a part title at all? Reference, an existing part1 in base
Second control: prev/next chapter linkschanged, troubleshooting.htmlfaq.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

ObligationWhereOwner
If /how-it-works takes the homepage’s architecture section, / must keep an #architecture anchorofficial-websiteAAASM-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 on docs.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 URLMeasured 2026-08-08Serves content?
    …github.io/agent-assembly/200, 2,896 bytesYes — landing page with a meta-refresh
    …github.io/python-sdk/200, 1,423 bytesYes — meta-refresh
    …github.io/node-sdk/200, 27,147 bytesYes — a full live page
    …github.io/go-sdk/200, 3,329 bytesYes — meta-refresh
    The fifth row — the pre-rename docs host, spelled out in MIGRATION.md404No — that repository was renamed under AAASM-4341

    The fifth row’s literal host is not written here on purpose: check_repo_names.py audits every tracked page for retired repo names and MIGRATION.md is 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.md names only agent-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 and MIGRATION.md.

    Not in the five, and worth knowing before someone re-derives this list: …github.io/docs/ 301s to docs.agent-assembly.com already. It is not one of AAASM-3665’s five rows, so it neither contradicts nor satisfies them.

  • The host-level www redirect is proposed, not applied.

  • Core’s own book has no [output.html.redirect] either, which its migration slice needs before its three published Move/Merge pages 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:

PageHomeAlso linked from
risk-scenarios.mdEvaluateVerify, to its negative-control section
source-of-truth.mdReferenceEvery section, for its area’s maturity
compatibility.mdReferenceEvaluate, Operate, Verify
open-core-boundary.mdEvaluateOperate, 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.md framed 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 that README.md section, 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.

Docs Hub — mdBook

ConstraintValueWhy
Sidebar entries, total36 after this design — 24 existing + this page + 3 new routers + 2 from AAASM-5609 = 30 page entries, plus 6 part titlesBelow a viewport height at 1080px, the sidebar scrolls; on mobile the whole tree renders in one drawer
Nesting depth under a part2 levelsA third level is a drawer nobody scrolls to
Section headingsNot linkspart-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 decorationLink text onlyNo badge mechanism exists, hence the label rule for 🗺️ Planned
BreadcrumbsNo mechanismmdBook 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 labelsNo channel beyond link textAAASM-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.mdNot supportedBuild aborts at exit 101; see the language route

Product website — Docusaurus

ConstraintValueWhy
Navbar itemsUnchanged 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 viaThe Product navbar item becoming a three-entry dropdown, a role chooser block on /, and the footerA dropdown of three is not a mega menu; the two existing mega menus stay as they are
Footer columns3, unchanged; /trust and /maturity join ResourcesA fourth column wraps below 768px
LocaleEvery new route exists in en and zh-HantThe 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.

TicketRepository and pathsRoutes or sectionsMust not touch
AAASM-5608docs: docs/src/SUMMARY.md; new docs/src/{integrate,operate,verify}.mdThe six sectionsAny existing page body
AAASM-5611docs: bodies of README.md, security-model.md, comparison.mdSUMMARY.md
AAASM-5609docs: two new evaluator guides; policy-reference.mdFirst two chapters of EvaluateSUMMARY.md ordering beyond its own two entries
AAASM-5586official-website: src/pages/product.tsx, new how-it-works route; docs: policy-reference.md/product, /how-it-works/, the role routes
AAASM-5585official-website: src/pages/index.tsx, src/components/home/**/Navbar, footer, /product
AAASM-5587official-website: new role route filesFour role routes/, /product, navbar
AAASM-5596official-website: docusaurus.config.ts, src/components/MegaMenu/menus.ts, _headers; docs: _headers, docs/book.tomlNavbar, footer, canonical links, redirectsAny page body
AAASM-5658docs: glossary.mdSUMMARY.md
AAASM-5610docs: metadata blocks on existing pagesPage prose
Unassigned — use casesofficial-website: new /use-cases routes/use-cases and children
Unassigned — trustofficial-website: new /trust route/trust
Unassigned — maturityofficial-website: new /maturity route/maturity
Unassigned — walkthroughdocs: end-to-end governance walkthroughOperate

The two overlaps, both named rather than left to be discovered.

  1. policy-reference.md is named by both AAASM-5586 and AAASM-5609, because documentation-inventory.md assigns 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.
  2. 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 the AA-PAGE-META block 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.

GapDisposition
GAP-1 — no surface routes all six audiencesClosed 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-2Getting Started holds only 🗺️ Planned pagesClosed. 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 publishedCarried 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 surfaceCarried 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 modelCarried forward. security-model.md keeps the security-engineer entry slot in Evaluate; the rewrite is AAASM-5611’s
GAP-6 — no roadmap surfaceCarried forward, and relocated. It closes at L1 as /maturity, not on this hub. Unassigned
GAP-7 — SDK mounts absent from the sidebarNot 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 AboutClosed 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.

audienceEntryNextEscalation
evaluatorEvaluate — What Ships Today, or product-promise.md until it landsrisk-scenarios.md, open-core-boundary.mdADR 0033 §5.3 and §6, in Core
security-engineerEvaluate — security-model.mdrisk-scenarios.md, then VerifyCore’s threat model, the manifest, verification reports
auditorVerify — verify.mdsaas-claim-publication-checklist.md, risk-scenarios.md’s negative controlThe manifest and verification reports (L6 — GAP-4)
operatorOperate — operate.mddocker-containers.md, self-host-observability.mdCore’s quick start and CLI reference
developerIntegrate — integrate.mdThe SDK mount for their languageCore’s API reference, the examples repository
contributorContribute — page-standards.mdaudiences.md, this pageADR 0034, content-ownership.md, claim-vocabulary.md

How this page meets its acceptance criteria

CriterionHow it is met
Each top-level item corresponds to a user task or decisionThe 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 pagesOne 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 separatedThe 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 consideredNavigation 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 ticketsThe partition. Thirteen rows naming their own files, a landing order, and two overlaps named rather than counted as cleanpolicy-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

ToWhat
AAASM-5608The 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-5611That 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-5587The route table, the four-role count and the shared-prefix requirement, the #architecture anchor obligation, and the navbar budget
AAASM-5596The 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-5609That 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-5013The layer split across its three surfaces, and that its canonical scenario’s approval leg rests on a term no manifest row reaches
AAASM-4237The three questions a sitemap settles, and the four that remain its own
AAASM-5601 · AAASM-5610That 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.

PathModuleGenerator
/This hubmdBook
/core/agent-assembly (core monorepo)mdBook
/python-sdk/python-sdkmkdocs-material
/node-sdk/node-sdkDocusaurus
/go-sdk/go-sdkHugo + Hextra
/arena/arenamkdocs-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.

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.

  1. Extract the English source into a po/messages.pot template:

    cd docs
    MDBOOK_OUTPUT__xgettext__pot_file=messages.pot mdbook build -d po
    
  2. Translate per-language po/<lang>.po catalogs (for example po/zh-Hant.po). Untranslated strings are left empty and fall back to the English source at build time.

  3. Build one localized site per language by overriding the book language, which activates the gettext preprocessor (configured in book.toml):

    MDBOOK_BOOK__LANGUAGE=zh-Hant mdbook build -d book/zh-Hant
    

    The 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 gettext preprocessor as a no-op (there is no po/en.po), so contributors who only touch English content still just run mdbook build. Producing or previewing a translated build additionally requires the mdbook-gettext binary (cargo install mdbook-i18n-helpers).

Contributing a translation

Translations are community-contributed. To improve the zh-Hant draft or add a new language:

  1. 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
    
  2. Fill in the msgstr entries in po/<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.

  3. Validate with msgfmt -c po/<lang>.po and 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