Cross-Agent Hive — Design Specification
Status: Epic #521, phases #522–#528. Phases 1 (PR #530), 2 (PR #531) and 3 (PR #532) shipped; the rest proposed. The adapter-registry half of phase 3 was split out as #533. Written against the code as of bc61737d (v0.79.0), and against Codex CLI 0.162.1 as installed on a real machine — every claim about Codex's on-disk shape in §9 was measured, not inferred.
Scope: Let coding agents that are not Claude Code — starting with Codex — join a hive as full members: contribute what they learn, receive what others learned, and talk to every other member over one bus. Today the hive's substrate is agent-neutral but its two ends are not: capture happens through a Claude Code hook, and application happens into a Claude Code prompt.
Implementation status
| Phase (§8) | Issue | Status | Module / artifact |
|---|---|---|---|
| 1. Canonical tool vocabulary | #522 | Shipped (PR #530) | claudectl-core/src/agent/vocab.rs, seam at brain::preferences::canonical_tool_for |
| 2. Unit provenance + artifact applicability | #523 | Shipped (PR #531) | src/hive/mod.rs (origin_agents, ArtifactRequires::agents), hive/distiller.rs (OriginIndex), claudectl-core/src/agent/mod.rs (presence detection) |
| 3. Codex discovery + monitor | #524 | Shipped (PR #532) | claudectl-core/src/agent/codex.rs, session::ClaudeSession::{from_codex_rollout,is_actionable} |
| 3b. Route everything through the adapter registry | #533 | Not started | src/coord/adapter*.rs, or claudectl-core/src/runtime.rs |
| 4. Hive read surface for any agent (MCP) | #525 | Not started | src/hive/mcp.rs (new), mirroring src/query/mcp.rs |
| 5. Codex write surface (AGENTS.md, rules) | #526 | Not started | src/hive/project/ (new) |
| 6. Bus: agent + machine dimension | #527 | Agent half shipped (Codex --self, roles.agent); machine half not started |
src/bus/{store,roles,policy}.rs |
| 7. Bus: relay bridge (cross-machine) | #528 | Not started | src/bus/bridge.rs (new), src/relay/mod.rs message type |
1. Motivation
The project's tagline is "learns from you once and remembers everywhere." Everywhere currently means every Claude Code session on every machine you have paired. It does not mean every agent you use.
That gap is not academic. On the machine this spec was written against there are 671 Codex rollout transcripts and a Codex approval store with 275 entries. Every approval in that store is a preference the user expressed — the exact raw material brain/preferences.rs distills into a KnowledgeUnit — and none of it reaches the hive. In the other direction, a preference the hive has learned with high confidence from a hundred Claude sessions has no way to reach a Codex session, which will ask about the same command again.
The two agents also share MCP servers, which is where this gets both worse and better than expected. Most of Codex's MCP calls already carry the exact Claude Code tool name (mcp__chrome-devtools__click), so those preferences would merge the moment either side contributed one — they simply never do, because Codex never contributes. The rest arrive split across two fields (namespace: "mcp__cua_repl", name: "js") and need one join to become the same key (§3.2). Either way it is the same tool on the same server, and the hive currently treats the two agents' experience of it as unrelated.
This spec covers three questions in order of how expensive they are to get wrong:
- How is knowledge represented so that the same preference learned in two different agents merges into one unit? (§4 — the spine, and the only irreversible decision here.)
- How is knowledge captured from an agent that has no PreToolUse hook? (§5)
- How is knowledge applied to an agent that cannot be gated? (§6)
And one more that the user asked for directly, and that turns out to be mostly a plumbing problem rather than a design problem:
- How does every agent, on every machine, reach one bus? (§7)
2. What already crosses the boundary
Most of the hive is agent-neutral, and it is worth being precise about that before describing what is not — the work here is smaller than "support another agent" sounds.
| Component | Agent-neutral? | Why |
|---|---|---|
hive/store.rs, gossip.rs, merger.rs, trust.rs, membership.rs |
Yes | They move KnowledgeUnits between peers. Nothing in them knows what a session is. |
KnowledgeScope (hive/mod.rs:384) |
Yes | Universal / Language / Project. No agent dimension is wanted (see §4). |
KnowledgeContent::{Pattern, Insight, Temporal, PromotedRule, ApproachOutcome, ApproachCluster} |
Yes in shape | tool is a free-form String; the rest is prose and statistics. Only the vocabulary is Claude-specific. |
KnowledgeContent::Skill |
Yes, today | Codex and Claude Code use the same SKILL.md contract — ----delimited frontmatter with name and description. Verified in §9.3. |
DecisionRecord (brain/decisions.rs:77) |
Yes in shape | tool: Option<String>, command: Option<String>, project, user_action. Nothing Claude-shaped. |
claudectl --brain-query --tool X --command Y --project Z |
Yes | A shell-callable decision endpoint. Any agent that can run a subprocess can already consult the brain. |
claudectl bus stdio (bus/mcp.rs) |
Yes, today | An MCP stdio server. Codex loads MCP servers from config.toml, so a Codex session can already publish and read_inbox. See §7.1. |
Two things that look like cross-agent support but are not:
src/coord/adapter.rshasAgentAdapter,AgentFamily::{ClaudeCode, Codex}, and a stubCodexAdapter. It is the right seam and it is nearly dead: the only callers aresrc/coord/cli.rs:1061and:1079, both inside acoord adapterslisting command. Nothing in discovery, monitor, brain, or hive routes through it.src/brain/agents.rs(AgentConfig,[agents.codex]in config,run_agent) is delegate-to Codex: shell out with a prompt, capture stdout. That is the opposite direction from observing a Codex session you did not start, and it is a different feature. Conflating the two is the most likely way to misread this spec.
3. The five couplings
3.1 Discovery
claudectl-core/src/discovery.rs:6 hardcodes ~/.claude/sessions/*.json (PID-named) and resolves transcripts under ~/.claude/projects/<slug>/<uuid>.jsonl. scan_subagents hardcodes /tmp/claude-{uid}. The ClaudeSession type name is load-bearing across both other crates.
Codex's equivalent is richer than expected (§9.1): ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl, with session_meta carrying session_id and cwd, event_msg/token_count carrying model_context_window alongside full token usage, and task_started / task_complete / turn_aborted as status signals.
Phase 3 settled three things this section originally left open, and one of them contradicted the plan.
There is no process to discover
This spec and #524 both assumed Codex discovery would find processes and read their state. Measured: the ChatGPT desktop app runs Codex as one long-lived codex … app-server serving every session, alongside 27 Electron helpers. Nothing distinguishes which session a process owns. A terminal codex is its own process, but there is no general mapping.
So a Codex session is discovered from its transcript, pid is 0, and the session is not actionable — ClaudeSession::is_actionable(). This is a safety property, not a missing feature: offering "kill" on a Codex session and reaching the shared app-server would end every Codex session the user has open.
Liveness is a recency window, because there is no session-end event
task_complete ends a turn. A session the user closed is byte-identical to one sitting idle. CODEX_SESSION_TTL_SECS (30 minutes) is the whole liveness model, chosen from the measured distribution — 0 rollouts touched in the last hour, 1 in four, 25 in a week, 671 all time. It is bimodal, so the exact value barely matters. Its one cost: a session that crashed mid-turn reads as working until the window expires.
Cost is absent, not zero
Codex rollouts carry no USD. Codex is subscription-billed and records only tokens. cost_usd stays 0 with usage_metrics_available false and has_known_cost() false, so nothing can mistake "unknown" for "free". The existing cost sums are additive, so a 0 contributes nothing and no total is corrupted.
The type was not renamed
ClaudeSession keeps its name (§10 question 5, now closed). It is public API across all three crates, a Codex session fills the same fields and flows through the same views, and renaming buys nothing an agent_family field does not. Three predicates keep the distinctions from drifting: is_claude_code() for "where do this session's files live", is_actionable() for "may I touch it", is_remote() unchanged for "is this from a peer".
3.2 Tool vocabulary — the one that matters
semantic_key for a Pattern is pattern:{tool}:{cmd} (hive/mod.rs:549-556), and tool is literally "Bash", "Write", "Edit". Codex names the same operations differently. The measured table (§9.2, from 15 000 tool calls across 120 rollouts):
Codex name (and namespace) |
Observed count | Claude Code equivalent |
|---|---|---|
exec_command |
7046 | Bash |
exec |
3624 | none — a JS sandbox, see below |
write_stdin |
1336 | none — keystrokes, see below |
apply_patch |
1270 | Write or Edit — see below |
shell_command |
671 | Bash |
update_plan |
60 | TodoWrite |
spawn_agent, collaboration/{spawn_agent,followup_task} |
~100 | Task |
collaboration/{send_message,wait_agent,list_agents} |
~140 | none — messaging, not delegation |
request_user_input_async |
31 | AskUserQuestion |
web/run |
27 | WebSearch |
wait, clock/sleep |
251 | none |
The rule: map only a genuinely identical operation
A tool is mapped only when it is the same operation and its command payload is comparable. Everything else keeps a family-prefixed name (codex:exec) and merges with nothing.
This is deliberately conservative. A false merge corrupts a unit other machines rely on; a missed merge only leaves knowledge local, and is recoverable later by extending the table. When in doubt, do not map.
Three Codex tools fail that test despite looking mappable, and reading their payloads is what settled it:
| Tool | Payload | Why not mapped |
|---|---|---|
exec_command |
{"cmd": "ls"} |
— mapped to Bash; a shell command string |
shell_command |
{"command": "ls", "workdir": …} |
— mapped to Bash; same |
exec |
const r = await tools.exec_command({…}) |
A JavaScript sandbox, not a shell. Mapping it would file JS snippets under the same key as shell commands. |
write_stdin |
{"session_id": 76793, "chars": ""} |
Sends keystrokes to an already-running session. Not a command, and Bash has no equivalent. |
collaboration/send_message, wait_agent |
— | Messaging and waiting, not delegation. Only spawn_agent and followup_task are what Task does. |
web/run is the one deliberate imprecision: it is Codex's single web tool where Claude Code splits WebSearch from WebFetch. Search is the dominant use, and a missed merge on fetch costs less than filing fetches under search would.
MCP tools mostly need no normalization at all
Codex emits MCP calls in two shapes, and the dominant one is already the Claude Code spelling byte-for-byte (§9.2):
Codex namespace |
Codex name |
Count | Canonical |
|---|---|---|---|
| (absent) | mcp__chrome-devtools__evaluate_script |
139 | identical — pass through |
mcp__cua_repl |
js |
381 | mcp__cua_repl__js |
So the rule is: if name already begins with mcp__, it is the canonical name and must pass through untouched. Otherwise, when namespace begins with mcp__, join as {namespace}__{name} — the namespace already carries the mcp__ prefix, so this is a join, not a reconstruction.
This matters because the Claude Code form is not reversibly splittable: a server segment may itself contain _ and - (mcp__chrome-devtools__new_page, mcp__claude_ai_Claude_Docs__batch), so there is no way to recover (server, tool) from the joined string. Normalization therefore has to run forward from Codex's explicit fields and never attempt the inverse.
The practical consequence is good: most MCP tool preferences already share a key across the two agents and will merge the moment a Codex session contributes one.
apply_patch splits into Write and Edit, and the signal is present
Claude Code keeps Write (new file) and Edit (existing file) as distinct tool names in DecisionRecord.tool, so existing hives hold separate pattern:Write:* and pattern:Edit:* units. Mapping every apply_patch to Edit would silently merge Codex file-creation approvals into Edit units.
It does not have to be lossy. Codex's patch envelope carries the distinction explicitly, and in the sample (§9.2) it is well populated: *** Update File 1934, *** Add File 190, *** Delete File 10. So:
| Envelope marker | Canonical |
|---|---|
*** Add File |
Write |
*** Update File |
Edit |
*** Delete File |
codex:apply_patch_delete |
Deletion has no Claude Code tool analogue — it happens through Bash rm, which is a different operation with different risk — so it keeps a family-prefixed name and merges with nothing. That is correct rather than unfortunate.
A patch touching several files can carry several markers. Where markers disagree, prefer Write: a batch that creates a file is the higher-privilege operation, and a preference learned about it should not be filed under the gentler name.
The flow has exactly one seam, where DecisionRecord.tool becomes PreferencePattern.tool and then KnowledgeContent::Pattern.tool. Before phase 1 it read:
and it is now canonical_tool_for(d). Normalization runs on the read side deliberately: the decision log keeps the name the agent actually used, so the log does not lie about what happened, and a wrong table entry is fixed by re-distilling rather than being baked into every record forever.
Phase 1 also showed the seam covers more than Pattern — removing the normalizer splits the ToolAccuracy key (accuracy:{tool}) the same way.
3.3 Capture
Decisions are produced by claude-plugin/hooks/scripts/brain-gate.sh, a Claude Code PreToolUse hook. Codex has no pre-tool gate. Its only event hook is notify, which fires after the fact — on this machine, notify = [<client>, "turn-ended"].
So the hive cannot gate Codex the way it gates Claude Code. That is a property of Codex, not a gap in claudectl, and the design has to accept it rather than work around it.
What Codex does have is its own approval memory: ~/.codex/rules/default.rules, 275 entries, every one of the form
This is machine-written, not hand-authored — entries include full heredoc bodies and absolute-path mkdirs nobody types into a config file (§9.4). It is a distilled approval corpus in a format claudectl can both read and write, which makes it both a capture source and an application target.
Caveat, flagged because phase 5 depends on it: the file's last write on this machine was 2 Sep, and state_5.sqlite contains no approvals table. Current Codex may have moved approval memory somewhere else, or may simply not have had a new approval to record. Verify before building on it; phases 1–4 do not depend on it.
3.4 Application
src/brain/engine.rs:388 calls hive::injection::build_hive_context_for_session and puts the result in brain_ctx.hive_context — the brain's own LLM prompt, not the agent's context. The agent only ever sees the brain's verdict, delivered through the hook.
For an agent with no hook, the hive has to reach it another way. Three targets, in increasing order of privilege:
- An MCP server (
claudectl hive stdio). Pull-style: the agent asks. Needs no hook, no file mutation, and works with stock Codex because Codex loads MCP servers natively. This is the right first move. - A managed block in
AGENTS.md. Prose knowledge, pushed. There is already precedent for marker-delimited managed blocks in that file on this machine —<!-- paper-lantern:start -->— so the pattern is established and the convention is<!-- claudectl-hive:start -->…<!-- claudectl-hive:end -->. - Emitting
prefix_rulelines into~/.codex/rules/. Native enforcement without a hook: a high-confidence "always approvecargo test" unit becomes a Codex rule and Codex stops asking. This is real privilege — claudectl would be writing another agent's approval store — and must be explicit opt-in, never a default, never silent.
3.5 Artifact applicability
KnowledgeContent::HookConfig is Claude-Code-only by nature: there is no Codex PreToolUse to install into. ArtifactRequires (hive/mod.rs:406) carries cli, os, and min_version but no agent field, so a hook unit gossiped to a Codex-only peer has no way to declare itself inapplicable. It will arrive, fail to install, and look like a bug.
4. The spine: one vocabulary, no agent in the key
There is no version on the gossip wire. hive/gossip.rs:555 writes version: 1 on the unit, not on the protocol. And semantic_key is the cross-peer merge key — store.rs:84, distiller.rs:120, archive.rs:147 and the merger all key on it. Changing how it is derived splits a hive between old and new binaries, silently, with both halves believing they are in sync.
That single fact determines the design:
Do not add an agent dimension to
semantic_key. Normalize foreign tool names into the existing vocabulary at capture time.
The reason is the product, not the schema. "cargo test is always approved," learned in Codex, has to land in the same unit as the one learned in Claude Code. If the key carried the agent, the hive would hold two units, each with half the evidence, each decaying on its own clock, and "learns once, remembers everywhere" would be false in exactly the case the feature exists for.
Three consequences:
canonical_tool(family, namespace, name, payload) -> Stringis a pure function living inclaudectl-core, with the §3.2 tables as data. Claude Code's names are the canonical form, because they are already on the wire in every existing hive. This is not a claim that they are better names; it is that they are the incumbent ones and a rename is a migration. Thepayloadargument exists only soapply_patchcan read its envelope marker (§3.2) — every other mapping is a function of the name and namespace alone.- Provenance is additive, never in the key.
KnowledgeUnit.origin_agent: Option<String>with#[serde(default)]. Old peers ignore the field; new peers can show "learned in Codex, confirmed in Claude Code," which is more trustworthy than either alone and should eventually raise confidence rather than merely annotate it. - Applicability is per-artifact, not per-unit.
ArtifactRequires.agents: Vec<String>(#[serde(default)], empty = any) onSkill/Command/HookConfigonly. A hook unit declaresagents = ["claude-code"]and a Codex-only peer skips it on arrival instead of failing to install it.
Two details phase 2 settled that the first draft left implicit:
Attribution is per-approach, not per-tool. origin_agents is joined onto finished units from the decision log, keyed on canonical tool plus command keyword — the content part of a Pattern's key. Keying on the tool alone would make a Codex-only cargo fmt approach inherit claude-code simply because Bash is a shared tool.
Provenance is cumulative across a merge, but only where evidence is. A replacing merge unions both units' agent lists, since "confirmed independently in two agents" is the case the field exists to record. A rejected unit's origins are not folded in: its evidence did not transfer, so claiming its agent would overstate where the stored knowledge came from.
Agent presence is detected by state directory, not by PATH. ~/.claude and ~/.codex existing is the better signal for what the answer is used for — a skill installs into that directory and is read from it on the agent's next run, so an agent whose binary lives somewhere unusual (a version manager, a wrapper, a GUI bundle) should still receive artifacts.
An unnormalizable tool — Codex's wait, or any future agent's unique primitive — keeps its raw name prefixed with its family (codex:wait). It will not merge with anything, which is correct: it has no counterpart.
5. Observe: adapters that produce neutral records
src/coord/adapter.rs already declares the trait. Two things have to change for it to carry real weight.
It has to move, or grow a core half. ClaudeCodeAdapter wraps claudectl-core's discovery::scan_sessions. A Codex adapter needs the same: rollout parsing belongs in claudectl-core/src/agent/codex/, not inside src/coord/adapter_codex.rs, because core cannot depend on the binary and the TUI needs to render Codex sessions too. The adapter in src/coord/ stays a thin binding.
It has to be on the path, not beside it. Today nothing but a listing command calls it. Discovery, monitor, the brain's observe loop, and the TUI session table all need to go through all_adapters() for the trait to mean anything. That is the largest mechanical change in this spec and the one most likely to want its own epic.
The capability matrix already models partial support honestly (AdapterCapabilities::minimal()), which is what Codex needs: discover_sessions and monitor_state yes, send_input and deliver_interrupt no, until there is a mechanism for them.
6. Apply: project a unit into each agent's native surface
Symmetry is the wrong goal here. Each agent has a different enforcement surface, and the hive should use the best one each offers rather than the lowest common denominator.
| Claude Code | Codex | |
|---|---|---|
| Gate a tool call before it runs | PreToolUse hook → --brain-query |
not possible |
| Pull knowledge on demand | — | claudectl hive stdio (MCP) |
| Standing prose instruction | CLAUDE.md |
AGENTS.md managed block |
| Standing approval | brain gate + session-policy |
rules/default.rules (opt-in) |
| Install a skill | ~/.claude/skills/ |
~/.codex/skills/ |
The skills row works today except for one hardwired path: hive/cli/share.rs:487-490 (default_install_dir() → ~/.claude) and the scan roots at claudectl-core/src/skills.rs:79-86. Widening both to a per-family set of roots makes skill sharing cross-agent with no change to the wire format, which makes it the cheapest visible win in this spec.
7. One bus everyone can use
The bus today is single-machine and Claude-shaped in exactly one place. Both are smaller problems than they look.
7.1 Any agent, same machine — mostly works already
claudectl bus stdio is an MCP server. Codex loads MCP servers from config.toml. A Codex session can therefore already call whoami, list_agents, publish and read_inbox with no code change — this needs documenting more than building.
One thing does break. bus/roles.rs:116:
pub fn find_claude_ancestor_pid() -> Option<u32> {
ancestor_pids().into_iter()
.find(|pid| process_command_for(*pid).is_some_and(|c| c.to_lowercase().contains("claude")))
}
bus role bind --self walks the ancestor chain for a process whose command line contains claude. From a Codex session there isn't one, so the bind fails. The fix is to match a set of known agent families and return which one matched — the family is worth recording on the role anyway (§7.2).
7.2 Roles need an agent and a machine dimension
The roles table (bus/store.rs:69-77) is keyed on role alone, with cwd_selector and a nullable pid for resolution. Both are machine-local concepts, so supervisor on a laptop and supervisor on a Mac mini are the same row, and a role bound on one machine resolves to nothing on the other.
Three additive columns — agent_family, origin_peer, and a peer_id-qualified address form — let a message be addressed to reviewer@mini or reviewer:codex while leaving bare reviewer meaning what it means today. The migration pattern already exists in the file (add_column_if_missing, used for the pid column), and the coord schema's PRAGMA user_version gate is the precedent for refusing to run against a newer schema.
7.3 The relay bridge
There is no bridging between the bus and the relay. Verified: zero relay:: references in src/bus/, zero bus:: references in src/relay/, and relay::MessageType has no bus variant. The bus is SQLite over a local file; the relay is authenticated TCP between machines. They have never met.
A bridge is a new MessageType::BusMessage plus a forwarder, and two existing pieces do most of the hard part:
- Loop prevention is already built.
bus/policy.rs:31definesDEFAULT_MAX_HOPS = 8andvalidate_hop_count, and themessagestable already carrieshop_count. A forwarded message increments it; the cap that exists for agent-to-agent chains works unchanged for machine-to-machine ones. - Trust is already built. The relay's HMAC challenge-response and the hive's membership tiers decide who may forward. A bus bridge should refuse to forward from a
hive.readmember for the same reasonKnowledgeRejectedexists (#435): a reader contributing silently is worse than a reader being told no.
What needs deciding, and should be decided before it is built: whether the bridge is replicating (every peer's SQLite converges, which means conflict resolution and a durable ordering) or forwarding (a message addressed to a remote role is relayed on demand, and the local database stays local). Forwarding is far simpler, matches how send_to already works, and is almost certainly right for the first version — but the reserved-role guard (bus/policy.rs:41, supervisor and operator cannot be bound) means a cross-machine supervisor address has no owner, so that interaction needs an answer either way.
Known trap: one-shot relay dials have no reply path — send_to fails by design and TransientReplies is the mechanism. A bus bridge that forwards a publish and expects an ack will hit exactly this, and it is the same shape as the still-open TaskHandoff instance in epic #478.
8. Phasing
Ordered by irreversibility, not by size.
- Canonical tool vocabulary (§4). Pure function, table-driven, no wire change, no new dependency. Testable against real rollout fixtures. Ships alone and unblocks everything else. Do this first — it is the only decision here that is expensive to reverse.
- Provenance and applicability (
origin_agent,ArtifactRequires.agents). Both#[serde(default)], so old peers ignore them. Small, and wanted before any foreign unit exists. - Codex discovery and monitor in
claudectl-core, wired through the adapter trait. The largest piece; may want its own epic. Cost fields degrade to tokens-only (§3.1). claudectl hive stdio— the read surface, mirroringsrc/query/mcp.rs. Lowest-risk application path, works with stock Codex.- Codex write surfaces —
AGENTS.mdmanaged block, thenrules/default.rulesemission behind explicit opt-in. Depends on verifying §3.3's caveat. - Bus agent/machine dimension (§7.2). Additive columns plus a resolver change. Independent of 1–5; could go first if the bus matters more than the knowledge sharing.
- Bus relay bridge (§7.3). Decide forwarding vs replication before writing code.
Skill-root widening (§6) is a one-file change and does not need a phase; it can ride with whatever lands first.
Feature gates. Adapters sit behind coord; the hive sits behind relay; the bus sits behind bus. Cross-agent hive lives in the intersection of all three, which no current CI job exercises together. That needs a build job before phase 3, or the gates will rot the way the ones in the #465 audit did.
9. Evidence
Everything below was measured on one machine (macOS, Codex CLI 0.162.1) on 2026-10-13. It is recorded here because the next person to touch this will otherwise re-measure it.
9.1 Codex session layout
One JSON object per line, each with timestamp, ordinal, type, payload. Event types observed:
type / payload.type |
Carries |
|---|---|
session_meta |
session_id, id, cwd, runtime_workspace_roots, parent_thread_id |
event_msg / token_count |
total_token_usage{input,cached_input,cache_write_input,output,reasoning_output,total}, last_token_usage, model_context_window |
event_msg / task_started, task_complete, turn_aborted |
status transitions |
response_item / function_call |
name, namespace, arguments, call_id |
response_item / custom_tool_call |
name, input, status, call_id |
response_item / message, reasoning |
conversation content |
token_usage_record |
per-turn usage |
turn_context, world_state |
turn configuration |
model_context_window being present means context-pressure health checks work for Codex with no new signal. No field carries cost in USD.
9.2 Tool-name histogram
120 rollout files sampled across all months, ~15 000 tool calls. Counts in the §3.2 table. Kinds: function_call 10 101, custom_tool_call 4894, local_shell_call 0.
A second pass over 168 files recorded the raw namespace and name fields rather than a joined display string, which is what §3.2's MCP rule is built on:
381 namespace='mcp__cua_repl' name='js'
139 namespace=None name='mcp__chrome-devtools__evaluate_script'
120 namespace=None name='mcp__chrome-devtools__take_snapshot'
75 namespace=None name='mcp__chrome-devtools__click'
10 namespace=None name='mcp__codex_apps__github_search'
Two shapes, and the common one is already the Claude Code spelling. An earlier pass of this measurement printed mcp__context7/query_docs; the / was the measuring script's own join separator and is not a Codex delimiter. Recorded here because the mistake is easy to repeat.
The same pass counted apply_patch envelope markers: *** Update File 1934, *** Add File 190, *** Delete File 10, *** Move File 0.
9.3 Skill format comparison
~/.codex/skills/docker-container-ops/SKILL.md
---
name: docker-container-ops
description: Launch, manage, exec into, and listen to Docker containers. …
---
~/.claude/skills/braintrust/SKILL.md
---
name: braintrust
version: 1.0.0
description: Use the Braintrust `bt` CLI for projects, traces, prompts, …
---
Same contract. Claude Code's optional version is absent in Codex skills and ignored by them, so a shared skill round-trips.
9.4 Codex approval store
~/.codex/rules/default.rules 275 lines, 46 KB, mtime 2 Sep
275 x prefix_rule(...)
275 x decision="allow" (no deny entries)
No header or comment. Machine-written: entries include multi-kilobyte heredoc bodies and mkdir -p /Users/<user>/.codex/skills/... with absolute paths. state_5.sqlite tables are threads, thread_sections, thread_attachments, thread_dynamic_tools, thread_spawn_edges, projects, project_roots, project_idempotency_keys, external_agent_config_imports, remote_control_enrollments, backfill_state, rollout_migration_*, _sqlx_migrations — no approvals table, so the rules file remains the only approval store found.
9.5 Other Codex integration points
~/.codex/AGENTS.md— standing instructions. Already contains marker-delimited managed blocks (<!-- paper-lantern:start -->), so the convention is established.~/.codex/config.toml—mcp_servers.*(many configured),notify = [<client>, "turn-ended"],approvals_reviewer = "auto_review".~/.codex/session_index.jsonl—{id, thread_name, updated_at}per thread. A cheap index for listing without parsing rollouts.~/.codex/plugins/,~/.codex/memories/— not examined; possible later surfaces.
10. Open questions
- Does current Codex still write
rules/default.rules? §3.3's caveat. Blocks phase 5 only. - Forwarding or replication for the bus bridge? §7.3. Decide before writing code.
- Who owns a cross-machine reserved role?
supervisorandoperatorcannot be bound (bus/policy.rs:41), sosupervisor@minihas no resolution story. - Does
origin_agentaffect confidence, or only display? A unit confirmed independently in two agents is better evidence than one confirmed twice in the same agent. Saying so in the scoring is defensible; it is also a scoring change, which is a separate kind of risk from a schema change. - ~~Does
ClaudeSessionget renamed?~~ Closed by #524: no. Anagent_familyfield plus three predicates costs less than renaming public API across three crates, and is reversible if a rename later earns itself. - ~~What happens to a Codex session's cost in the fleet snapshot?~~ Closed by #524:
cost_usdis 0 withhas_known_cost()false. The existing sums are additive so nothing is corrupted, and anything that needs to tell "unknown" from "zero" has a predicate to ask. - Should the adapter registry be
runtime::SessionSourcerather than a second trait? Raised by #533.SessionSourcealready answers "where do sessions come from", is already core-owned, and is already implemented by the binary — a second registry for the same question would be the duplication #533 exists to remove.
Related
Epic #521 is this spec's tracking issue; phases are #522–#528.
Epic #478 (work across hosts — TaskHandoff is still the open instance of the no-reply-path trap §7.3 describes) · epic #424 (open cluster / named hives) · #435 (read-only members, the precedent for refusing a contribution rather than dropping it) · #465 (dead-code audit — coord/adapter.rs carries #![allow(dead_code)] and is the same shape) · #506 (LAN discovery written but never started — the same "exists but not on the path" shape as the adapter trait) · #511 (a one-shot send that is never acknowledged) · docs/relay-and-hive.md · docs/AGENT_BUS.md · docs/open-cluster.md