AxiOwl Run IDs and Message IDs Explained

AxiOwl uses two different identifiers when a message moves through the system: a run ID and a message ID. They sound similar, but they answer different questions. The run ID ties related work together. The message ID identifies one specific send attempt and becomes the receipt handle for that attempt.

That distinction matters because AxiOwl is not only passing text from one chat to another. It is resolving a sender, resolving a target, recording receipt boundaries, handing work to a delivery worker, and giving the receiving agent enough information to reply through AxiOwl without guessing where the reply belongs.

The short version

A message ID is created for each send. In the current C++ implementation, MessagePipeline::send assigns receipt.message_id = new_message_id() before validating the target and body. That ID appears in send receipts, delivery-stage logs, delivery-worker request files, evidence logs, and reply helper text as receiptForMessageId.

A run ID is the correlation ID for the broader exchange. A caller can provide it with the CLI option --run-id or the MCP field runId. If the caller does not provide one, the message pipeline creates one with the same internal ID generator used for message IDs. The run ID is then carried through delivery-stage records, delivery-worker handoff data, provider smoke-test reports, and the reply helper shown to the receiving agent.

In practical terms: use the message ID when asking, "What happened to this exact send?" Use the run ID when asking, "Which sends, replies, and checks belong to the same operation?"

Where the IDs enter AxiOwl

The CLI exposes run IDs directly:

axiowl send --to <agent> --body <message> [--from <agent>] [--from-node <node>] [--strict] [--run-id <uuid>]

The MCP tool exposes the same correlation field as runId:

{
  "to": "Target Agent",
  "body": "Message body",
  "runId": "example-run-id"
}

The MCP schema also includes receiptForMessageId. That field is logged on inbound MCP sends as receipt correlation, while runId is pushed through to the CLI as --run-id. The MCP tool description is deliberately conservative: success is an MCP-to-AxiOwl handoff receipt, not proof that the provider delivered the message or that a reply has arrived.

What a message ID proves

When AxiOwl accepts a send, the CLI prints an AxiOwl handoff receipt with a message ID. The implementation is careful about what this receipt means. The normal accepted state is accepted_by_axiowl, and the CLI text says provider delivery, provider wake-up, and provider reply are not implied.

That boundary is enforced in tests. The core test suite 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, starts background delivery after the receipt, and does not wait for a provider result before returning to the sender.

So the message ID is not marketing fluff. It is the handle for the exact accepted request. It follows the request into delivery.jsonl, events.jsonl, delivery-worker files, and delivery-worker events. If support or an operator needs to trace a failed or delayed send, the message ID is the first thing to search.

What a run ID connects

The run ID is broader than one send. In the message pipeline, every delivery-stage record includes both run_id and message_id. A provider smoke test uses one run ID across several provider attempts, then writes a Markdown report and JSONL records under names that include that run ID.

That makes the run ID useful for multi-step operations. A single run can include an initial message, a reply, a provider smoke-test contract, timeout handling, and evidence gathered from several providers. Each individual send still has its own message ID, but the run ID lets those records be grouped without relying on timestamps or display names.

This is especially important in AxiOwl because registry display names and provider sessions are separate concepts. The architecture docs describe AxiOwl as a local Windows coordinator with a durable registry of provider sessions, provider-specific delivery edges, discovery repair, and delivery logs. A run ID gives operators a stable thread through those moving parts.

How reply instructions use both IDs

Before delivering a normal message to a provider, AxiOwl builds a final visible body. If the sender identity is resolved, that body includes the original message plus an exact MCP reply instruction:

mcp__axiowl.axiowl_send_message({
  "to": "<sender>",
  "body": "<full reply>",
  "runId": "<same run ID>",
  "receiptForMessageId": "<message ID being replied to>"
})

The tests explicitly assert that the helper body includes both runId and receiptForMessageId when they are present. This is the clean split in action: the reply stays inside the same run, and it also says which exact message it is answering.

That is why AxiOwl should not ask chats to invent their own session IDs or sender IDs. The MCP server instructions say the host MCP session ID is the stable sender identity key, and the provider support matrix says final support requires provider-owned metadata. Run IDs and message IDs are correlation tools; they do not replace sender identity.

Where to look in logs

On Windows, AxiOwl state lives under %LOCALAPPDATA%\AxiOwl. On Linux-style environments, it uses $XDG_STATE_HOME/axiowl or ~/.axiowl. The active implementation defines these important log paths:

logs/events.jsonl
logs/delivery.jsonl
logs/create-lifecycle.jsonl
logs/activation.jsonl

The delivery log is the best place to follow the staged path of a message. Delivery-stage records include fields such as run_id, message_id, provider, target, session_id, node, stage, result, detail, and source.

The evidence log records events such as MCP inbound receipts, AxiOwl acceptance, targeted discovery behavior, delivery-worker handoff, worker results, and rate-limit delays. Those records also carry message IDs, and delivery-worker events carry both run IDs and message IDs.

Practical operator habits

When starting a coordinated test or investigation, pass an explicit run ID. It can be any clear unique string your team can recognize. That makes later searches easier than relying on an auto-generated hex value.

For example:

axiowl send --to "Receiver Agent" --from "Sender Agent" --body "Please confirm receipt." --run-id "support-case-2026-07-08-001"

After the command returns, keep the printed message ID. The run ID identifies the case; the message ID identifies the first send in that case. If the receiver replies through the helper instruction, the reply can carry the same run ID and receiptForMessageId pointing back to the original send.

For automated provider checks, the built-in provider smoke-test command already follows this pattern. It accepts --run-id, uses that run ID in the exact reply contract, tracks each provider attempt by message ID, and writes both a Markdown report and JSONL evidence for the run.

What not to assume

Do not treat an AxiOwl handoff receipt as end-to-end delivery proof. A receipt means AxiOwl accepted and routed the request far enough to start delivery work. Provider acceptance is a separate result. A reply over MCP, with correct provider-owned sender identity, is stronger proof that the target received the message and responded through the intended path.

Do not use run IDs as authentication or identity. They are correlation values. Sender identity still comes from MCP metadata, provider patches, explicit sender addresses that resolve to registry rows, and targeted discovery repair when appropriate.

Do not use message IDs as conversation IDs. A provider session has its own provider-owned session ID in the registry. A message ID is per send. It is useful precisely because it is narrower than a chat session.

Why the split is worth having

AxiOwl coordinates multiple providers, bridges, local and remote surfaces, registry rows, sender identities, and delivery stages. A single identifier would either be too broad to debug one send or too narrow to connect a multi-message workflow.

Run IDs and message IDs keep those jobs separate. The run ID gives the operation a spine. The message ID gives each send a receipt. Together they let AxiOwl return quickly, log honestly, and still preserve enough correlation for operators and agents to understand what happened next.