Why AxiOwl Uses Independent Agents Instead of Nested Subagents

Nested subagents are useful when one parent agent breaks work into smaller assignments and collects the results. AxiOwl is solving a different problem. It is built to let real provider sessions send messages to one another as independent peers, with explicit sender identity, target identity, provider-specific routing, and evidence about what actually happened.

That difference is not just language. It shows up throughout the AxiOwl C++ repo. The product model is not "parent starts child, child reports back." It is "a named sender sends a text body to a named target, AxiOwl resolves both sides, chooses one provider edge, records evidence, and returns an honest receipt."

Sources read

Source repo: C:\Users\kjhgf\OneDrive\Documents\postfourth\axiowl-cplus

External sources: none.

The unit is a real provider session

AxiOwl's architecture overview describes a local Windows coordinator for sending messages between AI provider sessions. The registry model makes that concrete. In models.hpp, an AgentRecord is not a temporary helper role. It has a display_name, aliases, provider, provider_session_id, node_id, sendable flag, source, timestamps, and last_error.

That shape matters. A Codex thread, Cursor composer, VS Code native session, VS Code Copilot-backed session, Antigravity conversation, or CLI session can each be represented as its own addressable row. discovery.cpp builds a stable agent id from provider, node id, and provider session id. registry.cpp stores those fields in a TSV registry whose header includes agent_id, display_name, aliases, provider, provider_session_id, node_id, enabled, sendable, source, first_seen_at, last_seen_at, last_verified_at, and last_error.

That is the opposite of hiding work inside a parent execution tree. AxiOwl wants the target to remain a real session in its own provider, with its own context and its own route.

Why nested subagents are the wrong runtime model

The nested subagent pattern is centered on delegation. One owner starts workers, gives them scoped prompts, receives their outputs, and integrates the final result. That is a good orchestration pattern for research, review, and parallel analysis.

AxiOwl's runtime model is centered on addressability. The important questions are different:

Those questions require independent agent records, not nested workers. If AxiOwl treated targets as children of a parent agent, it would blur the identity boundary that the product depends on. The sender and target are separate sessions. A reply is not a hidden return value from a child. It is a new message through the same routing system.

The product contract in docs/plans/01-product-contract.md states this directly: a named sender sends a text body to a named target, AxiOwl resolves both names, builds the final visible message, uses exactly one provider edge, records what happened, and returns an honest receipt. It also says AxiOwl must not invent unknown sender names, silently fall back to another provider family, or wait for provider-generated replies as part of send success.

Sender identity has to survive the trip

Nested subagent systems can often rely on the parent process to know which worker it launched. AxiOwl cannot rely on that. It has to know which real provider session should receive a reply later.

That is why mcp_server.cpp is strict about identity. The MCP server exposes axiowl_send_message, and its initialization instructions tell hosts to use typed AxiOwl tools, not raw command arguments. The instructions also say the host MCP session ID is the stable sender identity key, and that AxiOwl resolves that key to the registered visible sender name before appending reply instructions.

In the actual send handler, handle_send_message resolves identity from MCP metadata and host config, rejects empty targets or bodies, registers the sender, then runs the CLI send path with --from, --from-node, and --stdin. If identity is missing, it fails loudly instead of letting the chat invent a session id.

The developer docs reinforce the same rule. Sender identity should resolve from provider-owned MCP metadata first, then from an explicit provider session id that matches the registry, then from an explicit sender address or alias, then from targeted discovery repair. Environment-only identity injection is not considered final support for provider CLI paths.

That is a design reason to prefer independent agents. Each sender needs a stable identity that can be mapped back to a provider session. A child role inside a parent conversation is not enough.

The registry protects session boundaries

The registry is more than a convenience list. docs/plans/03-discovery-and-registry.md calls it the source of truth for target routing and summarizes the routing shape as:

agent name -> provider -> provider session id -> node

registry.cpp implements lookup across display names, aliases, exact provider session ids, and node ids. It prefers sendable rows where possible. It can distinguish duplicate display names because provider session ids remain first-class. The core tests include a case where two rows have the same display name but different sessions; name lookup picks the newest row, while exact session lookup still returns the older exact session. That is only possible because the sessions are modeled independently.

Discovery follows the same discipline. discovery.cpp can add newly found sessions, refresh rows, enrich manual rows, and log enrollment decisions. It also distinguishes discovery evidence from sendability. A discovered row is not automatically a safe target. The support matrix says a provider is not supported merely because AxiOwl can write a config file or start a process; the current bar includes discovery, install/config, send, provider receive, MCP reply, and correct provider-owned sender identity.

Nested subagents usually optimize for task ownership. AxiOwl optimizes for session ownership.

The send pipeline resolves, then routes

The common user-facing send shape is simple:

axiowl send --to "Target chat name" --body "Message text"

For longer bodies, cli.cpp supports stdin:

@'
Full message with code, JSON, quotes, or lists.
'@ | axiowl send --to "Target chat name" --stdin

Behind that simple command, MessagePipeline::send enforces the routing contract. It rejects an empty target or body. It resolves sender identity through the registry or explicit metadata. If the sender looks like an unresolved session id, it runs one targeted sender discovery repair and logs the result. It resolves the target through the registry, runs one targeted target repair if needed, and rejects the send if the target still is not found or is known but not sendable.

Only after those boundaries are satisfied does the pipeline set accepted_by_axiowl = true. The receipt state is accepted_by_axiowl, and the detail is axiowl_receipt_only. The log records that the receipt boundary is after target resolution and before the delivery worker or provider. The core test test_send_returns_axiowl_receipt_then_starts_delivery_handoff protects that behavior: a sender receipt must not claim provider acceptance and must not wait for provider delivery.

This is not a nested return path. It is a message handoff with explicit proof levels.

Provider edges keep peers independent

After AxiOwl resolves the target, provider selection is registry-driven. provider_edges.cpp normalizes the target provider and dispatches to the appropriate delivery module: Codex, Codex CLI, VS Code native, VS Code Copilot-backed, Cursor, Antigravity, Claude Code CLI, Copilot CLI, OpenCode CLI, or another implemented edge.

That dispatch table is important because each provider surface has different mechanics. AxiOwl does not pretend every session is the same kind of child worker. The provider edge owns the provider-specific delivery method, and the center of the product keeps the common contract: sender, target, final visible body, receipt, evidence, and reply path.

The current support matrix shows the same idea at the product level. Supported surfaces are listed as provider surfaces such as codex:agents, codex:cli, vscode:agents, copilot:vsix extension, cursor:agents, and antigravity:agents. Target surfaces remain provider sessions, not subordinate task slots.

Replies are new messages, not child returns

The AxiOwl docs are careful about receipt boundaries. docs/user/README.md says accepted_by_axiowl means AxiOwl accepted the message and handed it to the delivery layer. It does not prove the target provider displayed or processed the message. The stronger end-to-end proof is a provider reply through AxiOwl MCP with correct sender identity.

That proof model fits independent agents. The receiver is asked to reply through AxiOwl, and that reply starts another route with its own sender identity. docs/plans/02-message-pipeline.md describes the reverse direction as the receiver running AxiOwl send through the same fixed pipeline. There is no special response pipeline.

This is one of the cleanest reasons AxiOwl avoids nested subagents. In a nested system, the parent often expects a worker output. In AxiOwl, a reply is a separate provider-session message that must be identified, routed, and logged on its own.

Practical value for operators and developers

Independent agents make AxiOwl easier to operate in the situations it is built for.

An operator can list registered agents, inspect which provider and session id a target maps to, and see whether the row is sendable. A developer can reason about failures by stage: sender identity missing, target not in registry, target known but not sendable, delivery worker handoff failed, provider edge failed, or provider reply proof missing. The evidence model is explicit enough to avoid claiming more than the system can prove.

The model also limits context confusion. Sending to a Cursor session does not make it a child of a Codex session. Sending to a VS Code chat does not move that chat into a parent transcript. AxiOwl sends a final visible body to a target provider edge, then relies on the target's real provider environment and MCP reply path.

For developers, that keeps the architecture modular. cli.cpp owns commands. mcp_server.cpp owns MCP tool handling and identity extraction. message_pipeline.cpp owns validation, sender and target resolution, receipt boundaries, and delivery handoff. registry.cpp owns durable session lookup. provider_edges.cpp chooses the provider module. delivery_worker.cpp separates local receipt from provider dispatch. The system stays understandable because each independent session remains visible as data, not hidden inside an orchestration tree.

Closing

AxiOwl uses independent agents because the product is about real provider-session messaging, not parent-child task delegation. A target must be addressable as a provider session. A sender must resolve to a real identity. A receipt must say only what AxiOwl can prove. A reply must come back through MCP with enough metadata to route it.

Nested subagents are useful when one parent agent needs workers. AxiOwl is useful when existing provider sessions need to talk to each other without losing identity, routing discipline, or evidence. That is why the repo is built around registry rows, provider session ids, MCP metadata, provider edges, and honest receipts instead of nested runtime workers.