
How AxiOwl Receives Replies From Agents
AxiOwl receives a reply from an agent when that agent sends a new AxiOwl message back through the MCP tool. It is not chat scraping, hidden shared memory, or a guessed return path. The receiver sees a visible instruction in the delivered message, calls axiowl_send_message, and AxiOwl uses the MCP host metadata to identify which provider session is speaking.
That design is strict on purpose. A reply is stronger evidence than a local send receipt because it proves more of the path: the target provider received the message, had the AxiOwl MCP tool available, chose to answer through it, and supplied enough session identity for AxiOwl to route the answer back.
Sources read
README.mdapps/windows-desktop/README.mddocs/user/README.mddocs/developer/README.mddocs/security/trust-boundaries.mdapps/windows-desktop/extensions/codex-plugin/skills/axiowl/SKILL.mdapps/windows-desktop/src/models.hppapps/windows-desktop/src/mcp_server.hppapps/windows-desktop/src/mcp_server.cppapps/windows-desktop/src/cli.cppapps/windows-desktop/src/registry.cppapps/windows-desktop/src/message_pipeline.hppapps/windows-desktop/src/message_pipeline.cppapps/windows-desktop/src/final_visible_body_builder.hppapps/windows-desktop/src/final_visible_body_builder.cppapps/windows-desktop/src/evidence_log.hppapps/windows-desktop/src/evidence_log.cppapps/windows-desktop/src/delivery_worker.cppapps/windows-desktop/src/provider_edges.hppapps/windows-desktop/src/provider_edges.cppapps/windows-desktop/src/remote_relay.cppapps/windows-desktop/tests/core_tests.cppapps/windows-desktop/tests/mcp_conformance.ps1
The Reply Is Another AxiOwl Send
The important idea is simple: a reply travels through the same AxiOwl message system as the original message. The original sender becomes the reply target. The original receiver becomes the new sender.
The user docs describe this directly. AxiOwl discovers provider sessions, records them in a local registry, sends messages to selected sessions, exposes an MCP tool so providers can reply, and records receipts, logs, and delivery evidence. The same docs draw a hard line between local acceptance and a real reply: accepted_by_axiowl means AxiOwl accepted and handed off a message, but end-to-end proof is a provider reply through AxiOwl MCP with correct sender identity.
That means AxiOwl does not treat "message accepted" as "agent answered." It only treats a reply as received when the agent actually calls back through the MCP server.
How The Return Path Gets Into The Message
The return path is built into the final visible body that AxiOwl delivers to the target provider session.
MessagePipeline::send validates the message, resolves the sender, resolves the target registry row, and then calls build_axiowl_final_visible_body unless the request is already marked as final-visible by a relay path. For a resolved sender, the final body has three parts:
- A visible sender header, such as
Message from Sender Agent. - The original message body.
- A reply instruction that tells the receiver to use the exact MCP tool call.
The reply instruction is generated in C++ by build_axiowl_reply_instruction_block. Its shape is:
If requested to, respond with this exact MCP tool call:
mcp__axiowl.axiowl_send_message({
"to": "Sender Agent",
"body": "<full reply>"
})
When AxiOwl has a run id or message id, the helper adds runId and receiptForMessageId. That gives the reply a correlation trail without asking the receiving agent to invent anything.
The tests make this contract tighter. core_tests.cpp verifies that the resolved final body contains the MCP tool call, that quoted sender names are JSON-escaped, and that the old shell fallback wording does not return. It also checks that the body does not include axiowl send, --from, or old "command only" helper language. In the current implementation, the preferred reply path is the MCP tool because the MCP call carries host/session metadata.
If AxiOwl cannot resolve the sender, the builder does not attach a return command. It emits an unresolved sender warning instead. That is safer than giving a target agent a bad or ambiguous address.
Why The MCP Server Owns Sender Identity
An agent replying through AxiOwl does not provide --from. It should not guess its own session id. The MCP server derives that identity from the host tool call.
The MCP server initialization instructions say to use axiowl_send_message for sending and axiowl_create_agent for creating chats. They also state that the typed tools require the host MCP session id as the stable sender identity key, and that AxiOwl resolves that key to a registered visible sender name before appending reply instructions.
The bundled Codex skill says the same thing in agent-facing terms: prefer the AxiOwl MCP tool over shell commands because the tool receives Codex thread metadata. It tells agents not to know or invent their own Codex session id.
In mcp_server.cpp, handle_send_message does the reply intake work:
- read the tool arguments
toandbody; - reject an empty target or empty body;
- call
resolve_identityusing the MCP params and host config; - reject no-session sends when required identity metadata is missing;
- register the sender identity in the AxiOwl registry when possible;
- run the normal CLI send path with the body passed over stdin.
The hidden sender value passed to the CLI is derived from the resolved identity:
send --to <target> --from <sender-address> --from-node <node> --stdin
The receiving agent does not see or choose that --from value. The MCP server supplies it after it has resolved the host session.
What Session Identity Resolution Uses
The identity resolver accepts metadata from the MCP request _meta object. It checks _meta on the tool call params first, then _meta inside the tool arguments. That lets different hosts attach session metadata in the place their MCP bridge supports.
The resolver is provider-aware. It has branches for Antigravity, Antigravity CLI, Cursor, Cursor CLI, Claude Code CLI, Copilot CLI, OpenCode CLI, VS Code native, VS Code Copilot-backed, and Codex or Codex CLI style sessions. It normalizes provider-specific identifiers into stable session ids such as cursor_cli:session:<id>, antigravity_cli:conversation:<id>, vscode_native:session:<id>, and Codex thread ids.
For Codex, the implementation can use metadata from the call, Codex turn metadata, the local Codex SQLite thread table, and the AxiOwl registry. If a registered Codex CLI row matches the session, AxiOwl can use the registered display name. For other providers, the resolver uses provider-owned metadata keys such as conversation id, composer id, or session id. Some provider paths also read local session snapshots where that is the current integration method.
The important boundary is that this process is explicit and inspectable. If a provider-specific adapter expects a session id and the MCP metadata does not include it, the server records an adapter rejection reason and refuses the send. That is better than accepting a reply from "someone" and pretending the sender is known.
Registration Turns A Session Into A Sendable Name
Once the MCP server has a real session id and a real display name, it registers the sender through the registry command:
registry add-agent --name <sender-name> --provider <provider> --session <session-id> --node <node> --source mcp_sender_identity
If the raw sender address differs from the normalized session id, it is added as an alias. The raw MCP metadata is also recorded. The registry row stores the display name, aliases, provider, provider session id, node id, enabled/sendable state, source, timestamps, and last error.
This matters for replies because the original sender's display name is what appears in the reply helper's to field. The provider session id is what keeps that display name tied to an addressable session. When the reply arrives, sender_from_registry_or_explicit can map the MCP-derived sender address back to a registered display name. It prefers exact session matches and aliases when they prove the sender, and it avoids treating raw session-looking names as strong human names unless the registry explicitly supports that fallback.
The Message Pipeline Handles The Reply Like Any Other Message
After MCP intake, the reply enters MessagePipeline::send.
The pipeline starts by appending a delivery-stage event that says the MCP or CLI request entered the AxiOwl message pipeline and that provider delivery is not implied. Then it validates the target and body. Then it resolves the sender.
If the sender was not already resolved and looks like a session id, the pipeline can run one targeted discovery repair for the sender, then try resolution again. If the sender still cannot be resolved, it rejects the send. For final-visible relay bodies, it requires the relayed sender identity to be resolved before sending.
After sender resolution, the pipeline resolves the target. It looks in the registry, and if the target is missing or stale it runs one targeted discovery repair. It still requires a sendable target row before proceeding. If the target is known but not sendable, it fails loudly.
Only after sender and target are resolved does the pipeline set:
accepted_by_axiowl = true
state = accepted_by_axiowl
detail = axiowl_receipt_only
That local receipt is intentionally placed before provider delivery work. The pipeline then builds the final visible body for the target, packages a ProviderRequest, and hands it to the delivery worker.
The Receipt Boundary Is Preserved
The reply path keeps two receipts separate:
- The MCP-to-AxiOwl receipt says the tool call reached AxiOwl and the CLI send path returned.
- The provider delivery evidence says what happened when AxiOwl tried to dispatch the message to the target provider.
The MCP tool description says success is an MCP-to-AxiOwl handoff receipt only, and provider delivery and reply are not implied. The CLI output says the same thing: provider delivery, provider wake-up, and provider reply are not implied.
The tests enforce this boundary. test_send_returns_axiowl_receipt_then_starts_delivery_handoff checks that a send receipt is accepted by AxiOwl, does not report provider acceptance, logs delivery_receipt as axiowl_only, records the receipt boundary as after_target_resolution_before_delivery_worker_or_provider, and does not wait for a provider delivery result before returning.
That is why a reply matters. A local receipt proves AxiOwl accepted and routed a message. A reply through MCP proves the target provider session actually got far enough to call AxiOwl back with identity metadata.
Evidence For A Received Reply
AxiOwl records reply intake in the evidence log as JSONL. EvidenceLog::append writes an event_type, a UTC timestamp, and key-value fields.
When handle_send_message receives an MCP reply, it appends mcp_inbound_send_receipt with fields including:
- MCP request id;
- target;
- body byte count;
- sender session id;
- sender name;
- sender address;
- sender node;
- sender provider;
- host kind;
- identity source;
- run id;
- receipt-for-message id;
- source context;
- CLI exit code;
- whether AxiOwl accepted it;
- receipt boundary.
That last boundary is named mcp_tool_call_reached_axiowl_and_cli_returned_to_host. It is a precise statement. It does not claim the reply target already displayed the answer. It says the reply entered AxiOwl and the local send path returned.
The message pipeline also writes message_accepted_by_axiowl, delivery_handoff_started or delivery_handoff_failed, and delivery-stage events. The delivery worker writes request and provider delivery events around the final visible body. Those logs are what make a reply auditable instead of just conversational.
What The Delivery Worker Receives
Once the reply has been accepted locally, the delivery worker receives a request file with the target display name, target provider, target provider session id, node id, sender identity, run id, message id, strict flag, and final visible body.
Provider selection happens through provider_edges.cpp. The target registry row decides which provider module receives the request: Codex, Codex CLI, VS Code native, VS Code Copilot-backed, Antigravity, Antigravity CLI, Cursor, Cursor CLI, Claude Code CLI, Copilot CLI, OpenCode CLI, or remote handling where applicable. Unknown providers fail as unknown provider routes, and remote out-of-scope cases report that state instead of pretending they worked.
For remote relay sessions, remote_relay.cpp also preserves the body boundary. A relay deliver frame can mark body_is_final_visible; then the pipeline treats the body as already final-visible instead of appending another helper. Otherwise, the normal final body builder owns the visible sender line and reply instruction.
A Practical Reply Walkthrough
Here is the real sequence in plain language:
- Agent A sends Agent B a message through AxiOwl.
- AxiOwl resolves Agent A as the sender and Agent B as the target.
- AxiOwl builds the final visible body for Agent B.
- That body includes Agent A's name and an exact MCP reply call addressed back to Agent A.
- Agent B decides to reply and calls
axiowl_send_messagewithtoset to Agent A andbodyset to the full reply. - The MCP server receives the tool call and resolves Agent B from host/session metadata.
- The MCP server registers Agent B's identity if it has a real session id and display name.
- AxiOwl logs the inbound MCP send receipt, including any run or receipt correlation.
- The reply enters the normal message pipeline and is handed off toward Agent A.
The key operator lesson is that AxiOwl does not receive replies by assuming an agent responded. It receives replies when the agent uses the reply channel AxiOwl placed in the message.
Why This Matters
Agent coordination gets unreliable when sender identity is informal. "Tell the other chat to respond" is not enough if the system cannot prove which chat sent the response, which message it was responding to, or whether the original message ever reached the provider.
AxiOwl's current implementation makes the return path explicit. The final visible body gives the receiving agent a concrete MCP call. The MCP server derives sender identity from host metadata instead of accepting invented sender claims. The registry maps sessions to visible names. The message pipeline rejects unresolved senders and non-sendable targets. Evidence logs record the handoff and correlation boundaries.
That is the practical difference between a generic agent-to-agent prompt and a routed AxiOwl reply. AxiOwl receives the reply as a new, attributable message, then routes it with the same safeguards as the original send.
Closing
How AxiOwl receives replies from agents is mostly about discipline at the boundary. The target agent is shown an exact MCP reply call. The MCP server owns session identity. The registry turns that identity into a sender name. The message pipeline treats the reply as a normal send back to the original agent. Evidence logs preserve what happened without inflating a handoff receipt into proof of provider behavior.
That gives operators a clearer signal: a receipt means AxiOwl accepted a message; a reply through MCP means the agent reached back through the intended return channel.
Image prompt:
Create a polished graphic image related to: AxiOwl receiving replies from agents through an MCP message pipeline.
Subject: a technical routing console with two abstract computer terminals connected through a central message intake gateway, showing glowing reply packets entering the gateway and an evidence ledger panel represented by blank structured rows with no readable text.
Style: halfway between a clean symbolic icon and a realistic product/technical illustration; professional SaaS/technical marketing style; crisp edges; high detail; no text; no logos.
Background: solid #00ff00 chroma key green-screen background covering the full canvas edge to edge.
Restrictions: no owl, no axolotl, no birds, no animals, no mascot, no text, no watermark.