How AxiOwl Sends Messages Without Depending on Window Titles

Window titles are a tempting shortcut for desktop automation. They are visible, easy to search for, and usually close to the thing a human sees. They are also fragile. A provider can rename a chat, an editor can reuse a window, an empty window can have no useful title, and two sessions can expose similar text. AxiOwl is built around a different rule: use window-visible names for operator convenience, but route messages through registry records, provider-owned session identifiers, and provider-specific delivery edges.

That distinction matters because AxiOwl is not trying to click the right-looking window. It is a local message coordinator for supported AI provider sessions. The current architecture describes the flow as a CLI or MCP request entering axiowl.exe, passing through the MessagePipeline, resolving sender and target through the registry, running targeted discovery if needed, dispatching through provider_edges, and then using provider-specific delivery code. The route is data-backed before it is UI-backed.

The Registry Is The Routing Layer

AxiOwl keeps a durable local registry of known agents and sessions. In the implementation, the registry row includes fields such as display_name, aliases, provider, provider_session_id, node_id, enabled, sendable, source, last_seen_at, last_verified_at, and last_error.

The important part is that the human-readable name is only one field. It helps an operator type a command like:

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

But after that name enters AxiOwl, the message pipeline resolves it to a canonical registry row. The code searches both display names and aliases, but it also treats exact provider session matches as stronger than ordinary name matches. A target can be known but not sendable, and if so the pipeline refuses to deliver instead of guessing.

That refusal is intentional. A stale display name should not become a delivery path just because it looks plausible. The developer docs state the same rule directly: the registry is durable local state, not a cache-only convenience, and stale paths or stale display names must not become stronger than provider-owned session ids.

A Send Is Resolved Before It Is Delivered

The MessagePipeline::send path enforces several boundaries before provider delivery begins.

First, it validates that the target and message body exist. Then it resolves sender identity. For MCP replies, the preferred path is provider/session metadata, because replies need to come from a real provider session rather than a guessed display name. If sender identity cannot be resolved, the send fails loudly.

Next, AxiOwl resolves the target. If the target is missing, or if the row needs repair, the pipeline runs targeted discovery once. That is a narrow repair step, not a blind sweep and not a permission slip to guess. If discovery still does not produce a sendable exact target row, the pipeline returns a clear error such as a target not being in the registry after targeted discovery, or evidence being found without a sendable exact row.

Only after a canonical target is found does AxiOwl mark the route as resolved. It records the target display name, provider, provider session id, and node id in the delivery stage. If the row is not sendable, the pipeline returns target is known but not sendable, including the row's last error when available.

This is the core reason AxiOwl does not need to depend on window titles. The message is not sent because a desktop window currently says something. It is sent because the registry row says, "this provider session is the target, this provider edge knows how to reach it, and this row is currently sendable."

Provider Session IDs Replace Window Title Guessing

Different providers expose different stable handles. AxiOwl preserves those handles in provider-specific ways.

For Codex Desktop, the Windows provider requires the registry target's provider_session_id to be the Codex thread id. The delivery path connects to Codex Desktop IPC, initializes the client, and sends the message to that thread id. If the thread owner is not available, the code can nudge a Codex thread refresh and retry the IPC send. The evidence string is built around thread_id, owner probe details, and send results, not around a window caption.

For Antigravity, the provider code normalizes an Antigravity provider_session_id such as an antigravity:cascade: or antigravity:conversation: value into the recipient id used by the language server agentapi send-message call. It reads the live Antigravity log to derive the local agent API endpoint and token context, then calls the language server with the recipient id and final visible body. Again, the delivery key is the provider session id, not the text shown in a desktop title bar.

For Cursor, AxiOwl uses a bridge registry and queued command files. The Cursor bridge records a bridge_id, surface, workspace information, capabilities, session id, command directory, and result directory. The C++ provider writes a command file containing the operation, provider session id, cursor composer id, target agent, target bridge id, workspace fields, requested message, and delivery method sequence. The extension executes only commands meant for its bridge or workspace and writes a result file. That gives AxiOwl a bridge-level acknowledgement and a wrong-bridge guard, which is much stronger than "the active window looked right."

For VS Code native chat, AxiOwl uses a bridge with capabilities such as sending to an active chat, sending to an existing native session, starting a native chat, and reporting native chat session snapshots. Existing-session delivery goes through ownership resolution. The provider code checks bridge readiness, wakes the bridge when appropriate, resolves native session ownership, selects a bridge id when ownership is deliverable, and writes command/result files under the AxiOwl bridge root. The bridge reports machine/session/window scope and workspace storage data. That is deliberate ownership evidence, not a window-title match.

Display Names Still Matter, But They Are Not The Address

AxiOwl still uses display names. It has to. Humans need readable names, commands need memorable targets, and provider sessions often have user-facing chat titles.

The difference is that AxiOwl treats display names as lookup labels and evidence, not as the final routing authority. A registry row can have a display name and aliases, but provider delivery still depends on the row's provider and provider session id. The registry code even has logic to detect display names that look like raw session identifiers. For sender identity, a non-manual row whose display name is still just a session id is treated as unresolved enough to trigger repair instead of being trusted as a clean sender name.

That behavior is practical. During discovery, a provider may expose a session before its friendly title is available. During rename, a provider may update the visible title later than the underlying id. During restore, a chat may be reachable even if the desktop window text changes. AxiOwl can update names and aliases while preserving the stable provider session id that actually makes delivery possible.

Receipts Are Explicit About What Was Proven

AxiOwl also avoids another common automation trap: pretending that handoff means end-to-end success.

The user docs and architecture docs distinguish accepted_by_axiowl, provider acceptance, and a response over MCP. accepted_by_axiowl means AxiOwl validated the request and handed it to the delivery layer. A provider result such as accepted_by_provider means the provider edge reported that the target provider accepted the message. The strongest proof is a provider reply through AxiOwl MCP with correct sender identity, because that proves the recipient received the message, had AxiOwl available, and could identify itself on the way back.

This receipt model pairs naturally with session-id routing. AxiOwl can say exactly what it resolved, what provider edge ran, what provider session id was used, and what boundary was reached. If something fails, the failure is attached to a row, provider, session id, bridge id, result file, IPC owner probe, or agent API endpoint rather than to an ambiguous desktop title.

Why This Is Better For Operators

For an operator, the value is straightforward: fewer mystery sends.

If a target name is stale, AxiOwl can run targeted discovery once and then tell you whether it found a sendable target. If a row is known but disabled or evidence-only, it refuses to send. If a provider bridge is not active, the VS Code and Cursor paths can report bridge readiness, command paths, result paths, selected bridge ids, and ownership details. If Codex IPC cannot find an owner or cannot send to the thread, the result says that. If Antigravity cannot derive the live agent API endpoint, the error points to that dependency.

Those errors are more useful than "could not find window" or "clicked the wrong active window." They are also safer in a multi-provider desktop where several AI tools may be open at once.

The Practical Pattern

AxiOwl's current implementation follows a simple pattern:

  1. Let humans refer to agents by readable names.
  2. Resolve those names into durable registry rows.
  3. Require sender identity for replies.
  4. Repair missing targets through scoped discovery, not broad guessing.
  5. Dispatch by provider and provider session id.
  6. Use provider bridges, IPC, command files, or agent APIs as appropriate.
  7. Keep receipts honest about which boundary was actually proven.

That pattern is why AxiOwl can send messages across provider sessions without depending on window titles. Window titles may help a person recognize a chat. AxiOwl's delivery path is built on the registry, the provider session id, and the provider edge that knows how to reach that exact session.