Engineering
- Written for
developer- The decision it ends in
- Decide what adopting this costs me in my codebase, and which of my actions it actually reaches.
Where this starts
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.
What makes it this quarter’s problem
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.
What Agent Assembly does about it — and how far each answer reaches
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. The managed launch puts the process’s outbound traffic in front of the proxy, which is where the destination, credential and MCP entries apply. The first is in your code and is advisory; the second is out of your process and is where refusal actually holds.
Each card below is one entry of a sixteen-entry claim register shared by all four of these pages, quoted rather than paraphrased. The term is the register’s own, copied off the evidence rather than chosen to suit the sentence; the bound beside it is part of the claim, not context for it. Every entry — including the ones this page does not cite — is published with the manifest rows behind it: the shared claim register.
A tool call through a wrapped framework seam is checked before the tool body runs.
Where this stops. The SDK is advisory by design — a defence-in-depth posture, not the authoritative gate, and an agent that does not call it is not asking. Python raises before the body and fails closed. Go fails closed but requires an explicit WrapTools. Node’s default mode routes the check through an allow-all no-op client, so no refusal is produced there at all; asking for enforcement without a check-capable mode is refused loudly at init rather than silently allowed.
Launching a tool through aasm run writes the proxy settings into the tool’s environment, which is what puts its outbound connections on the path.
Where this stops. Writing a tool’s own settings file is tool governance, not a data-path claim; any prevention these adapters deliver is the proxy’s, borrowed through the launch environment. Of the shipped adapters, Claude Code is the only one above Integrated and the only one with a launch evidence test. Copilot’s launch always fails by construction. Codex and Windsurf inject the proxy variable with no CA trust, which is the configuration measured as failing the handshake silently. aasm run --no-proxy is an announced bypass. An unmanaged launch is a bypass and is not detectable.
A connection made on a path you routed through Agent Assembly is checked against the destination list you configured and refused before the proxy dials it.
Where this stops. The refusal is the proxy’s own local egress configuration, not a control-plane decision. The destination lists are empty by default — this refusal exists because an operator configured it. Linux release artifact; on macOS cargo install aa-proxy is the only route; on Windows there is no local mediation. If the proxy is not in front of the connection, the connection is simply made.
On the model-provider hosts Agent Assembly inspects, a recognised credential is removed from the request before it is forwarded.
Where this stops. Three built-in hosts, because llm_only defaults on. The default action is redact and forward, not refuse. Recall is bounded by the pattern set — there is no Stripe detector. Model responses on that path are not scanned.
An MCP tool call can be checked against your policy by the control plane and refused before the proxy forwards it.
Where this stops. The only gateway-bound pre-dial refusal in the product, and it is off by default. It reaches MCP sent as an ordinary HTTP/1.1 POST on an intercepted non-LLM host with a gateway endpoint configured. Tool servers over stdio — the most common setup — SSE and WebSocket have no interception mechanism; Streamable HTTP is recorded as functionally broken rather than merely uncovered.
What is different after
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.
What you can check, and where
Per-framework adapter status, per language, is the framework and coverage fields of the SDK rows in the capability manifest — read the row for your framework before you plan around it.
The capability manifest →Policy syntax and what it can express. Policy is versioned YAML or JSON you review through the Git workflow you already use, so a rule described on this site is a rule you can look up.
The policy reference →SDK-level detail per language — the Python, Node and Go documentation, including which modes produce a refusal and which do not.
SDK documentation →Where the SDK sits in the trust model, and why it is advisory rather than authoritative — stated in the architecture decision record, not inferred from a diagram.
ADR 0033 →
What you would otherwise find out later
- The SDK is advisory by design.It is a defence-in-depth posture, not the authoritative gate (RC5). 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. 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. Tracked as 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.Neither is a direct call that does not pass a patched seam. The wrapper reaches what it wraps and nothing else.
- Anything the SDK does not wrap is outside it.Raw HTTP, subprocess, filesystem, a database driver, browser automation from inside your process. That class is the reason the proxy exists, and on host actions there is no released mechanism at all.
- MCP over stdio is not on the mediated path.And it is the most common way tool servers are run (RC4).
- 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), which is the configuration measured as failing the handshake silently.
- Approval required is not something you can integrate against.Approval requiredRC12
No claim.
Where this stops. 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. AAASM-5657.
One page to read, one thing to do
Read the policy reference — what a rule can express is the boundary of what you can ask for, and it is shorter than most people expect.
The policy reference →Then pick your language’s SDK documentation and check your framework’s adapter row before you write against it — the deny signal differs by adapter, and Node’s default mode differs most.
SDK documentation →