How AxiOwl Discovers Existing Agents and Sessions

AxiOwl discovers existing agents by turning provider-specific session evidence into a local routing registry. It does not assume that every chat file, window, database row, or CLI history entry is safe to send to. The current C++ implementation looks for provider-owned session identifiers, checks whether the row proves a usable delivery target, records evidence and limits, and only exposes enabled sendable rows through axiowl list agents.

That distinction is the center of AxiOwl discovery: finding a possible session is not the same as enrolling a send target. AxiOwl keeps those states separate so an operator can see what was found without accidentally routing messages into stale, ambiguous, or unsupported provider surfaces.

Sources read

The Registry Is The Local Map

The registry is a TSV file under the AxiOwl state directory. On Windows, state_root() resolves to %LOCALAPPDATA%\AxiOwl; the agent registry path is:

%LOCALAPPDATA%\AxiOwl\registry\agents.tsv

On non-Windows builds, the state root falls back through XDG_STATE_HOME and then ~/.axiowl.

Each registry row is an AgentRecord. It carries:

That model keeps display names separate from provider identity. A user can send to a friendly name, but AxiOwl routes by provider, node, and provider session. It also lets the registry keep evidence rows that are useful for diagnosis but not valid as delivery targets.

The visible command reflects that policy:

axiowl list agents

run_list loads the registry and prints only rows that are both enabled and sendable. A discovered row that lacks proof remains out of this list. It can still appear in discovery output and logs, but it is not treated as an addressable agent.

What The Discover Command Does

The CLI exposes discovery by provider scope:

axiowl discover codex --json
axiowl discover vscode-native --target "Design Review"
axiowl discover all

The current command scopes are:

codex
codex-cli
vscode-native
vscode-copilot
copilot-cli
antigravity
antigravity-cli
cursor
cursor-cli
claude-code-cli
opencode-cli
all

For each scope, run_discover calls the provider-specific discovery function, then immediately runs merge_discovered_agents_into_registry. Plain output prints a summary with provider, node, rows found, rows enrolled, rows updated, rows skipped, issues, and the registry path. JSON output emits one discovered_agent line per row plus a discovery_summary line.

The discovery row type is deliberately more detailed than a name list. A DiscoveredAgent includes display name, provider, provider session ID, node ID, status, sendable flag, enrollment-proof flag, source, evidence strings, last-seen timestamp, and last error.

That lets AxiOwl say, for example:

Discovery is therefore not a silent fallback layer. It is a controlled pass that produces inspectable rows, registry changes, and evidence-log decisions.

How Rows Get Merged

The merge logic is the part that prevents discovery from becoming a risky overwrite operation.

A discovered row must have a display name, provider, provider session ID, and node ID. Missing identity fields are skipped. A row must also prove enrollment before it can create a new sendable target. The helper row_has_enrollment_proof rejects archived rows, rows with last errors, rows that are not sendable, and local Codex evidence-only rows.

When a row is accepted, AxiOwl builds a stable registry identity from:

provider:node_id:provider_session_id

It also adds aliases. Those include the display name, a simple "<name> agent" alias where appropriate, the full provider session ID, a bare session ID when the provider session ID has a prefix, and provider-specific aliases such as codex-thread:<session>.

If a discovered row matches an existing registry row by provider, node, and session ID, AxiOwl updates that row instead of duplicating it. If the provider now has a better display name, the old name is preserved as an alias. The test suite checks this behavior: a same-session discovery updates the existing row and keeps the old chat name as an alias.

If the same display name appears with a new provider session ID, AxiOwl enrolls a separate row. That is important because providers can reuse similar titles. The registry lookup then prefers the newest matching enabled sendable row when duplicate names exist.

Manual rows get special protection. If a row was added with source manual, discovery can enrich it with aliases and timestamps, but it does not overwrite the manual display name or downgrade sendability. The tests assert that a manual row remains manual and sendable even when weaker provider evidence arrives later.

Local Codex gets an additional safety rule. Some Codex sources are useful evidence but not enough proof for delivery. codex_active_workspace_vscode_thread and codex_active_codex_sqlite_thread_without_desktop_state are evidence-only unless a stronger path proves ownership or CLI resume support. Those rows are counted as evidence and may update an existing evidence row, but they do not create a new sendable registry row. The tests lock down that weak Codex evidence alone does not enroll a target.

VS Code native and VS Code Copilot-backed discovery also have stale auto-pruning. When a non-targeted discovery report comes from those providers, AxiOwl builds a set of currently proven provider session IDs and removes stale auto-discovered rows for the same provider and node. Manual rows are not pruned.

Provider Discovery Is Provider-Specific

AxiOwl does not use one universal scraper. Each provider surface has a different discovery source because each provider stores session identity differently.

Codex Agents

discover_local_codex looks at known Codex state database candidates under .codex, the session_index.jsonl, and .codex-global-state.json. It queries Codex thread rows from SQLite, reads names from the session index or state, and cross-checks desktop/global state references such as pinned threads, projectless threads, workspace roots, prompt history, thread-client IDs, and related state keys.

Codex discovery filters archived rows when possible and tries to find a usable display name from indexed names, SQLite titles, prompt-history titles, client prompt-history titles, or a working-directory-derived fallback. It rejects empty, unknown, untitled, newline-bearing, and session-ID-only names unless a targeted fallback is explicitly allowed.

The strongest local Codex path is desktop thread state. A targeted active-workspace Codex thread can also become enrollable when it exactly matches the requested target and AxiOwl can prove a Codex desktop IPC owner through the \\.\pipe\codex-ipc discovery request. Without that stronger proof, the row remains evidence-only.

Codex CLI

discover_codex_cli reuses the Codex state query but narrows the scope to exec-sourced threads or sessions already registered as codex_cli. It labels rows as codex_cli_exec_thread or codex_cli_registry_thread, sets the provider to codex_cli, and requires a usable display name or accepted session fallback before marking the row as enrollment proof.

The provider support matrix treats codex:cli as supported, but separately from Codex agents. CLI identity and metadata are not collapsed into the desktop agent surface.

VS Code Native

discover_vscode_native reads VS Code user storage under:

%APPDATA%\Code\User\workspaceStorage\*\chatSessions
%APPDATA%\Code\User\globalStorage\emptyWindowChatSessions

It scans bounded .jsonl files, extracts a sessionId or file-based fallback, and looks for visible titles such as customTitle or chat message text. The provider session ID is shaped as:

vscode_native:session:<session-id>

Rows are marked sendable and enrollment-proven only when the title is usable. Discovery also records ownership evidence from resolve_vscode_native_ownership, including ownership status, source kind, confidence, selected bridge ID, and evidence text. That ownership information matters because VS Code delivery has to target the correct window/session, not just any session file with a matching name.

VS Code Copilot-Backed

discover_vscode_copilot_backed reads Copilot session-state YAML under:

~\.copilot\session-state

It extracts the directory name as the session ID and reads a YAML name: field as the display name. The code uses vscode_copilot_backed as the provider, while the provider session ID currently uses the copilot_cli:session:<session-id> shape.

This path also checks VS Code Copilot patch status. If the patch status blocks send, the row is marked unhealthy, sendable is set to false, and last_error carries the patch reason. That is the right behavior: a session file alone is not enough when the bridge/patch required for delivery is unhealthy.

Antigravity Agents

discover_antigravity scans:

~\.gemini\antigravity\annotations

It reads bounded .pbtxt files, extracts titles from title:"..." or common JSON-like fields such as title, name, displayName, chatName, and conversationName, and creates provider session IDs shaped as:

antigravity:cascade:<session-id>

Rows with usable titles are active and sendable. The support matrix marks Antigravity agent sessions as supported, while warning not to confuse that with Antigravity CLI support.

Cursor Agents

discover_cursor reads Cursor global state from:

%APPDATA%\Cursor\User\globalStorage\state.vscdb

It queries ItemTable for composer.composerHeaders, extracts composer objects from allComposers or composers, and uses composerId plus name or title. Stable UUID composer IDs are treated as active sendable rows when not archived. Archived rows are not sendable. Non-UUID composer IDs become evidence-only with a clear last error.

Cursor discovery also checks patch status. If the Cursor patch blocks sending, the row becomes unhealthy and non-sendable. The support matrix marks Cursor agents as supported, but also calls out that Cursor internals are patch-sensitive and URI wake-up is fallback behavior.

CLI Provider Targets

AxiOwl has discovery code for several CLI surfaces, but discovery code does not automatically mean final supported status.

discover_antigravity_cli finds agy, reads ~\.gemini\antigravity-cli\cache\last_conversations.json, scans conversation .db files, and builds names like Antigravity CLI <workspace>. It marks rows stale if the recorded working directory no longer exists. The support matrix still lists Antigravity CLI as target, pending provider-owned metadata proof.

discover_claude_code_cli scans Claude JSONL files under ~\.claude\projects, accepts UUID-named sessions, looks for custom-title or agent-name events, and requires both a matching session record and an existing cwd before marking a row sendable. The support matrix lists Claude Code CLI as target, not fully supported.

discover_opencode_cli runs:

opencode session list --format json --max-count 100

It parses session JSON objects, builds opencode_cli:session:<id>, and requires a valid directory before keeping the row sendable. The support matrix lists OpenCode CLI as target.

discover_copilot_cli scans Copilot workspace.yaml files but filters to standalone CLI sessions by client_name values such as github/cli, copilot/cli, or cli. It also requires an explicit headless auth source such as COPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN, or COPILOT_PROVIDER_BASE_URL before marking a session sendable. The tests verify that a standalone Copilot CLI session without explicit headless auth is discovered as auth_unproven, not sendable.

discover_cursor_cli reads AxiOwl-owned Cursor CLI session records from %LOCALAPPDATA%\AxiOwl\cursor-cli\sessions.tsv and checks for a Cursor Agent executable plus an existing workspace. However, the provider support matrix currently marks cursor:cli as unsupported and explicitly says not to confuse Cursor editor/agent windows with a separate CLI provider. That matrix is the support source of truth.

Remote Is Not A Local Fallback

The codebase still contains remote node and remote discovery pieces. discover_remote_codex_target can SSH to a registered node and ask it to run:

axiowl discover codex --json --target <target>

It then wraps the remote provider and session into a local remote row.

But the public CLI behavior for this build is deliberately stricter. axiowl discover remote returns an out-of-scope report and exits with failure. axiowl discover all runs the local provider scopes and then prints a remote-excluded report. The local targeted miss resolution path also records that remote discovery is out of scope.

That matches the provider support matrix: remote is unsupported for local-provider remediation builds and must not be used as a fallback to hide local provider failures. Remote work needs an explicit contract, node validation, and its own support bar.

Discovery Feeds Installer Choices

The installer docs and behavior matrix make discovery part of the install safety model. Provider checkboxes should default from actual discovery evidence, not stale assumptions. A provider feature should be selected only when the app or CLI is detected, required paths exist, provider-specific gates pass, and the feature is not remote-only or unsupported by default.

Unchecked provider features should not be installed, patched, closed, restarted, or removed as collateral damage. That matters for providers like VS Code and Cursor, where selected features can require private patch-sensitive integration work. Discovery is not just a user convenience; it is how the installer avoids touching unrelated provider state.

The docs also state a hard limit: a successful install proves selected install actions completed. It does not prove every provider message path works. Full provider support requires discovery, install/config work, sending to a named provider session, provider receipt, a reply through AxiOwl MCP, and correct provider-owned sender identity.

Why This Design Matters In Daily Use

From an operator's point of view, AxiOwl discovery answers three practical questions:

  1. Which existing provider sessions can AxiOwl name?
  2. Which of those sessions are safe enough to send to?
  3. Which rows are only evidence, stale, unhealthy, unsupported, or out of scope?

The command surface keeps that workflow direct:

axiowl discover all --json
axiowl list agents
axiowl send --to "Existing Agent Name" --body "Please check this."

The first command inspects provider state and merges validated rows. The second shows only usable routing targets. The third depends on the registry row having a provider, node, provider session ID, and sendable status.

That is why AxiOwl discovery is not a generic "find my chats" feature. It is the bridge between messy provider session storage and a reliable local routing table. The current implementation is intentionally conservative: it enrolls what it can prove, keeps weaker evidence visible, protects manual operator choices, and refuses to claim provider support where the code and docs have not met the support bar.

Image prompt:

Create a polished graphic image related to: AxiOwl discovering existing agents and sessions.

Subject: a technical discovery console made of interlocking registry index cards, session tokens, and illuminated verification gates connected by clean circuit traces; no readable labels or symbols that resemble real logos.
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.