
AxiOwl Trust Boundaries Explained
A trust boundary is the line where software stops assuming and starts requiring proof. In AxiOwl, those lines are not abstract security slogans. They show up in the registry model, MCP sender identity handling, delivery receipts, provider adapters, remote-node routing, installer checkboxes, patch execution, and logs.
That design matters because AxiOwl sits between agent sessions. It accepts requests from a CLI or MCP host, resolves named agents through local state, builds a visible message, and hands that message to one provider edge. If the coordinator blurs its boundaries, it can overclaim delivery, send from the wrong sender, patch the wrong provider, or silently route through a fallback the operator did not choose. The current C++ implementation is built to avoid those failure modes by making each boundary explicit.
The Local State Boundary
AxiOwl is a local coordinator. On Windows, its state root is %LOCALAPPDATA%\AxiOwl. On non-Windows systems, the implementation uses $XDG_STATE_HOME/axiowl when available or ~/.axiowl otherwise. Under that state root, the code separates registry files, logs, runtime handoff files, create lifecycle logs, delivery logs, and activation state.
The main registry file is:
registry/agents.tsv
Remote node records live separately:
registry/nodes.tsv
Evidence and delivery logs are also separate:
logs/events.jsonl
logs/delivery.jsonl
That filesystem layout is a trust boundary. The agent registry is durable local state, not an invisible memory cache and not a provider database. A registry row stores a display name, aliases, provider, provider session id, node id, enabled flag, sendable flag, source, first-seen and last-seen timestamps, verification timestamp, and last error. AxiOwl can use that state to route messages, but it still has to distinguish "known" from "sendable" and "sendable" from "proven delivered."
The security docs list what AxiOwl may read and write. It may read local provider session metadata, provider config files, provider chat or session indexes needed for discovery, AxiOwl registry and log files, selected provider install paths, license activation state, and installer payload provenance. It may write AxiOwl-owned runtime files, logs, registry files, selected provider MCP config, selected bridge extension files, selected patch changes, and selected AxiOwl-owned provider config entries.
Equally important is what it should not touch: unrelated provider extensions, user auth tokens, unrelated provider settings, unrelated workspace files, provider chats except by sending user-requested messages, unchecked provider surfaces, and remote configuration unless explicitly selected.
The Sender Identity Boundary
The most important runtime boundary is sender identity. AxiOwl should know who is sending before it attaches reply instructions or routes a response path.
For MCP, mcp_server.cpp resolves identity from host and provider metadata. Different providers expose different keys: Antigravity uses conversation metadata, Cursor uses composer or session metadata, Claude Code CLI uses session metadata, Copilot CLI uses session metadata, OpenCode CLI uses session metadata, VS Code can use conversation/session storage, and Codex CLI can resolve through a registered thread or Codex sqlite thread snapshot. The exact key names vary, but the policy is consistent: the MCP server needs a usable provider/session identity.
When identity metadata is missing for a provider that requires it, the MCP server refuses the action. The error path is direct: the host did not provide a usable session id, so AxiOwl refuses to launch a no-session send or create a no-session body. The MCP initialization instructions reinforce the same boundary by telling hosts to use axiowl_send_message and axiowl_create_agent, not to construct raw axiowl send arguments manually, not to provide --from manually, and not to ask the chat to invent its own session id.
That is not just a UX preference. A raw display name can be stale. A copied session id can point at the wrong provider. A sender name that looks like a UUID may be a provider-owned identity key rather than a human-readable agent name. The registry code handles these cases by resolving sender identity through registered rows when possible and by marking unresolved sender sessions as unresolved rather than pretending they are safe visible names.
The Registry And Discovery Boundary
Discovery is useful, but AxiOwl does not treat every discovered hint as enough proof to send. The developer docs say not to mark a row sendable unless discovery or direct proof justifies it. The tests enforce the same idea.
For example, one core test verifies that weak Codex evidence alone is counted as evidence only and does not create a sendable registry row. Another test verifies that a targeted Codex row with IPC owner proof can enroll and become sendable. The registry merge tests also show a careful distinction between updating an existing session, keeping duplicate sessions when display names collide, and preserving manual rows instead of blindly overwriting them with discovered names.
The message pipeline uses discovery as a bounded repair step. If a target is missing or requires repair, MessagePipeline logs the miss and runs targeted discovery once. If the target is still missing, still requires repair, or only evidence-only rows were found, the send is rejected with a concrete reason. Discovery can repair state, but it is not a substitute for provider delivery proof.
That is a trust boundary operators should care about. It prevents stale paths, old display names, and partial discovery hints from becoming stronger than provider-owned session ids.
The Receipt Boundary
AxiOwl deliberately separates receipt types. The model has accepted_by_axiowl and accepted_by_provider as different fields, and the architecture docs define the difference.
accepted_by_axiowl means AxiOwl accepted and validated the request and handed it to the delivery layer. It does not mean the provider accepted the message. It does not mean the receiving agent saw the message. It does not mean the receiving agent replied.
Provider acceptance is a later boundary. It means the selected provider edge reported acceptance. The strongest proof is a response over MCP from the target provider with provider-owned sender identity, because that proves the target received enough context to act and send back through AxiOwl.
The C++ pipeline makes this explicit. When a request enters the message pipeline, AxiOwl logs that provider delivery is not implied. After target resolution and route selection, it records message_accepted_by_axiowl with a receipt boundary of after_target_resolution_before_delivery_worker_or_provider. Then it starts the background delivery handoff. The core tests assert that a normal send receipt reports AxiOwl acceptance, does not report provider acceptance, logs axiowl_only, and does not wait for provider result on the sender receipt path.
This is the difference between an honest coordination tool and a system that overpromises. AxiOwl can prove that it accepted a request before it can prove that a provider accepted it.
The Provider Edge Boundary
Provider-specific delivery lives behind provider edges. The dispatch table routes to providers such as codex, codex_cli, vscode_native, vscode_copilot_backed, antigravity, antigravity_cli, cursor, cursor_cli, claude_code_cli, copilot_cli, and opencode_cli. Unknown providers fail with an explicit unknown-provider result.
That provider edge is a trust boundary because each provider has different addressing, session state, wake-up behavior, and proof surface. AxiOwl's common pipeline does not quietly rewrite the route when a provider is difficult. It selects one provider edge based on the target registry row and records the result from that edge.
Remote routing is also explicit. One provider dispatcher path treats remote as out of scope for a local-provider remediation build. The dedicated remote provider module uses SSH, a node registry row, and axiowl relay-session --stdio when remote delivery is actually selected and configured. That means remote is not a silent fallback for local failure. It requires an enrolled node, an SSH path, and a target row that can be represented across the relay.
The MCP Process Boundary
AxiOwl's MCP server runs through process standard input and output. It can handle line-delimited JSON-RPC and framed MCP messages with Content-Length. The conformance test starts axiowl mcp-server --host codex --provider codex, checks initialize and tools/list, checks required tools such as axiowl_whoami, axiowl_send_message, axiowl_create_agent, and axiowl_version, and verifies framed and line protocols.
The server also has a hard frame-size guard. mcp_server.cpp sets the maximum supported MCP frame to 16 MiB, and the conformance test verifies that an oversized frame is rejected with a clear Content-Length limit error. That is a small but real trust boundary: a tool host can pass data through MCP, but not unbounded data.
The source also defines a reserved local service endpoint at 127.0.0.1:37661. Keeping that endpoint on loopback is the right shape for a local coordinator. The current implementation path is still centered on the executable, CLI, MCP stdio, registry files, and delivery worker handoff rather than a public AxiOwl control port.
The Remote Boundary
Remote AxiOwl delivery crosses a machine boundary, so the implementation uses SSH rather than an exposed AxiOwl listener. The remote module looks up a node in nodes.tsv, checks that host and SSH user are present, optionally uses the configured SSH key, and runs:
axiowl relay-session --stdio
The local side writes JSONL frames, including a hello frame and a deliver frame. The remote side reads stdin, validates the frame has a target and body, and returns a JSONL deliver result on stdout. If direct remote target metadata is present, the remote relay can call the provider delivery worker directly. Otherwise, it builds a normal MessageRequest and runs the local message pipeline on the remote node.
That keeps the boundary inspectable. Remote delivery requires an explicit node registry entry and an SSH command. It is not an open relay port, not a hidden mesh, and not a way to hide local delivery failures. The security docs state the policy directly: local provider support should remain local unless a remote feature is explicitly selected, and remote must not be used to hide local delivery failures.
The Installer Selection Boundary
The installer has its own trust boundaries. The installer behavior matrix says the MSI installs AxiOwl once, but each provider feature behaves like a separate install unit. Provider checkboxes should default from discovery, unchecked provider features should not be installed, patched, closed, restarted, or uninstalled as collateral damage, and selected provider features should fail loudly when they cannot be installed safely.
The installer safety script checks these rules in the WiX source. It verifies provider checkboxes, verifies that remote is unchecked by default, verifies selected install-step authorization flags such as --vscode-copilot-module-selected and --cursor-module-selected, and checks that unchecked checkboxes do not schedule provider removal. It also verifies that AxiOwl-owned process closing is explicit and that Restart Manager's generic app shutdown behavior is disabled.
For operators, the practical meaning is simple: selecting VS Code, Cursor, Codex, Antigravity, Claude Code CLI, Copilot CLI, OpenCode CLI, or remote support is supposed to define the scope of what the installer may touch. Discovery can inform defaults, but discovery alone should not force a feature into the install plan.
The Patch Execution Boundary
Some provider surfaces require patches because they do not expose a stable public API for the behavior AxiOwl needs. The trust-boundary docs name VS Code native/Copilot integration, Cursor Agent Window / Glass submit integration, and future CLI metadata support as patch-sensitive surfaces.
The VS Code installer intelligence code is a good example of how AxiOwl keeps patching bounded. Discovery is read-only and explicitly must not install extensions, patch JavaScript, write MCP config, delete stale files, or close processes. The decision types separate read-only discovery, planning, and ApplySelected mutation. The simulator checks that unselected providers remain untouched, that read-only scope rejects mutation, that the target exists and is readable, that hashes match the plan, that backups are required for mutation, and that process-close authorization exists when needed.
The executor refuses to run unless the provider is selected, mutation scope is ApplySelected, the plan has no fail-before-mutation actions, the simulation is safe to execute, and any required process close has been authorized. Before patching, it creates transaction journal entries and backups; after patching, it verifies the target text. If execution fails and the journal shows an unfinished transaction, it attempts rollback.
This is exactly what a patch boundary should look like: read facts first, plan separately, simulate safety, require selection, create rollback material, mutate only known targets, verify the result, and log evidence.
The Logging Boundary
AxiOwl's logs are not decoration. They are part of the trust model.
The message pipeline appends events for rejected sends, missing targets, targeted discovery, sender repair attempts, AxiOwl-only receipt boundaries, delivery handoff start or failure, rate-limit delays, and final body policy. The delivery worker appends request validation, provider dispatch, provider write success or failure, and provider result events. The registry stores last_error fields on rows. Installer actions write proof and warning logs under the AxiOwl log paths.
That matters because a failed trust boundary should be inspectable. If metadata is missing, AxiOwl should say that. If a provider is unknown, AxiOwl should say that. If a registry row is known but not sendable, AxiOwl should say that. If a remote node is missing host or SSH user, AxiOwl should say that. Silent fallback is the opposite of a trust boundary.
How To Think About AxiOwl Trust Boundaries
For day-to-day use, the boundaries are concrete:
- Local state is AxiOwl-owned state under the AxiOwl state root, not provider-owned secrets or unrelated workspace files.
- Sender identity must come from provider/session metadata, registered session rows, or explicit operator input that can be resolved safely.
- Discovery can repair registry state, but weak evidence does not automatically become a sendable target.
accepted_by_axiowlis not provider delivery proof.- Provider delivery stays inside the selected provider edge.
- Remote routing uses explicit node registration and SSH stdio relay, not a hidden public service.
- Installer feature selection defines what provider surfaces may be installed, patched, closed, or cleaned.
- Patch execution requires safe planning, selected scope, backups, verification, and evidence.
These boundaries are why AxiOwl can coordinate agent sessions without pretending every adjacent surface is equally trusted. The coordinator owns routing state, message handoff, and evidence. Providers own their session internals. Operators choose selected integrations. Remote nodes require explicit SSH-backed enrollment. Receipts say what was actually proven.
Closing
AxiOwl trust boundaries are implementation choices, not branding language. The current C++ repo shows those choices in the registry schema, MCP identity resolver, message pipeline, delivery worker, remote relay, installer matrix, patch simulator, and tests.
The result is a system that is easier to reason about under pressure. When AxiOwl sends a message, you can ask which identity was resolved, which registry row was used, whether the target was sendable, whether the receipt came from AxiOwl or the provider, which adapter handled delivery, whether remote was explicitly selected, and what logs prove the path. That is the point of drawing trust boundaries: not to make the system slower, but to make every risky crossing visible.