@cotal-ai/pi 0.33.7 → 0.33.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -2
- package/dist/standalone.js +2 -2
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -16117,7 +16117,7 @@ import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAll
|
|
|
16117
16117
|
|
|
16118
16118
|
// ../connector-core/dist/docs-bundle.generated.js
|
|
16119
16119
|
var DOCS_BUNDLE = {
|
|
16120
|
-
"version": "0.33.
|
|
16120
|
+
"version": "0.33.9",
|
|
16121
16121
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
|
|
16122
16122
|
"pages": [
|
|
16123
16123
|
{
|
|
@@ -16209,7 +16209,7 @@ var DOCS_BUNDLE = {
|
|
|
16209
16209
|
"title": "Connect Codex (beta)",
|
|
16210
16210
|
"kind": "Guide (informative)",
|
|
16211
16211
|
"summary": "OpenAI Codex joins a Cotal mesh as a lateral peer: the same cotal tool surface, the same message delivery and attention model as the other connectors, plus mid-turn steering (previously pi-only): a\u2026",
|
|
16212
|
-
"body": "# Connect Codex (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[OpenAI Codex](https://developers.openai.com/codex/) joins a Cotal mesh as a lateral peer: the\nsame `cotal_*` tool surface, the same message delivery and attention model as the other\nconnectors, plus mid-turn steering (previously pi-only): a directed peer message arriving\nmid-turn is **steered into the running turn** instead of waiting for it to end.\n\n**Beta** means the everyday path (spawn into the real Codex TUI, coordinate, watch) works; the\nspawn options that are not wired **fail loud** rather than degrade: resuming a session\n(`--resume`) and tool-sharing (`connectors.codex.mcpServers`). See [Limits](#limits).\n\n## Install\n\nThe connector ships with the CLI as a seeded extension (`@cotal-ai/connector-codex`): no\nseparate install step and no Codex-side plugin. You only need an authenticated `codex` binary\non your PATH (a ChatGPT-plan login or an `OPENAI_API_KEY`). If an older install is missing it,\n`cotal ext seed --repair` (or `cotal ext add @cotal-ai/connector-codex`) brings it in.\n\n**Don't install the `cotal` plugin Codex offers you.** Searching Codex's plugin list for \"cotal\"\nturns up a plugin named `cotal`, from the `cotal-mesh` marketplace. That is the **Claude Code**\nadapter, which appears there only because Codex reads the same plugin-marketplace format; it is\nnot this connector and installing it does not connect Codex to a mesh. Codex needs nothing\ninstalled on its side: the connector drives it from the outside, over `codex app-server`.\n\n**Codex version.** The connector drives `codex app-server` over its experimental v2 surface.\nMinimum **codex-cli 0.145.0**; tested against 0.145.0 and 0.146.0. An older binary authenticates fine but has\nno `--listen`/`--ws-auth` listener, so the launch fails at startup rather than misbehaving quietly:\ncheck with `codex --version` and upgrade (`npm i -g @openai/codex`) if a launch reports that the\napp-server exited before it started listening. The surface is explicitly experimental upstream, so\na later Codex release may change it and need a connector update. That is a break to report, not a\nsupport range we can promise ahead of it.\n\n## Spawn it\n\nSame launch grammar as any agent (see [run-a-mesh.md](run-a-mesh.md)):\n\n```bash\ncotal spawn --agent codex # foreground in this terminal\ncotal spawn reviewer --agent codex -d # detached via the manager; watch with `cotal attach`\nCOTAL_DEFAULT_AGENT=codex cotal spawn # make codex the default harness\n```\n\nOr set `agent: codex` in a team [manifest](manifest.md). Persona, role, and model come from the\nagent file as for any connector ([agent-files.md](agent-files.md)).\n\n## Choose a model\n\n```bash\ncotal models --agent codex # ids + reasoning-effort variants, via app-server model/list\ncotal spawn --agent codex --model gpt-5.6-sol --variant high\n```\n\nThe **variant** is Codex's reasoning effort (`minimal` | `low` | `medium` | `high` | `xhigh`).\nLike the `codex` CLI itself, the connector does not validate model ids or efforts locally. An\nunknown value fails at request time, server-side.\n\nModel and variant are published on presence, which is where `cotal roster` and the web dashboard's\n`model \xB7 variant` badge read them from. The variant appears only when you asked for one (via\n`--variant` or `variant:` in the agent file): there is no way to read the effort back off a running\nthread, so an unset variant is shown as absent rather than guessed at.\n\n## How it binds\n\nCodex has no in-process plugin runtime and its MCP client cannot wake an idle session, so the\nconnector runs Codex's own client/server split: a small **host process** embeds the mesh\nendpoint and drives a `codex app-server` thread over JSON-RPC (the same protocol the Codex TUI\nruns on). The app-server runs as an authenticated loopback **listener** rather than a private\npipe, which is what lets Codex's own TUI attach to the very thread the mesh is driving.\n\n- **Wake and steer.** An inbound batch starts a real turn (`turn/start`). A DIRECTED message\n (DM, anycast, @mention) arriving mid-turn is injected into the live turn (`turn/steer`);\n ambient channel chatter waits for the turn boundary so it can't derail work in flight.\n- **Native tools, one endpoint.** The host serves the shared `cotal_*` tools itself, on a\n bearer-authenticated loopback MCP endpoint (the token is passed by env name, so it never appears\n in the process table; see [Limits](#limits) for what that token does and does not protect). The model calls them like any tool and they\n execute against the host's single mesh endpoint: no sidecar process, no second identity. The\n app-server is the MCP client, so the tools work the same on a turn a peer message started and\n on one **you** typed into the TUI.\n- **Ready means on the mesh.** The host announces `ready` and hands the terminal to Codex only\n after the app-server, MCP surface, and mesh endpoint are all live (including the initial\n presence publish). If the broker cannot be reached, startup fails within 15 seconds with the\n broker address and latest connection error; it never opens an offline-looking TUI.\n- **At-least-once delivery.** A turn's surfaced messages are acked (by exact id) only when the\n turn completes. A failed turn retries with backoff, and an interrupted turn leaves the batch to\n redeliver. If the Codex app-server itself dies, the host restarts it in place (same mesh\n identity, credential, and durable) and re-drives the un-acked batch into the new thread; a\n crash *loop* (more than 3 in 2 minutes) is fatal rather than an endless respawn. (The shared\n bounded-inbox overflow rule applies: under extreme bursts an evicted in-flight id cannot\n redeliver.)\n- **Isolated, never written.** Each agent gets a private `CODEX_HOME` (one hashed directory\n per space+name under `.cotal/codex/`, rooted at the manager's workspace): your `~/.codex`\n config.toml, hooks, and MCP servers never load into a managed agent, and Codex's per-project\n trust records never touch your real config. Your `auth.json` is symlinked in (re-linked each\n launch), so ChatGPT-plan token refreshes never fork. Without an `auth.json` (or an\n `OPENAI_API_KEY`) the launch fails loud at thread start. Keyring-stored credentials are not\n wired through the isolated home; use the file store or the env key for managed agents. That\n symlink is why managed Codex agents are **POSIX-only** today: on Windows without Developer\n Mode the link fails, and the launch fails loud rather than copying `auth.json` (a copy would\n fork the token and break plan refreshes).\n- **Autonomy defaults.** Spawned agents run `approval_policy=never`,\n `sandbox_mode=workspace-write`, and `sandbox_workspace_write={network_access=true}`.\n See [Sandbox autonomy](#sandbox-autonomy) for what each one means and how to\n change it.\n- **It really is Codex.** `cotal spawn --agent codex` drops you into the actual Codex TUI,\n attached to the thread the mesh drives (`codex resume --remote`). Mesh turns render as they\n happen, and anything you type is a real user turn on that same thread with the `cotal_*` tools\n still available. In the foreground that is your terminal; detached it is the manager's pty,\n which is what `cotal attach` streams and drives. With no terminal at all (piped output,\n CI, a smoke) the host stays headless and prints an activity feed instead: the same peer either\n way, only the UI differs.\n **Which mode you get** is decided by whether *stdout* is a terminal, and `COTAL_CODEX_TUI=1|0`\n overrides that check when it would guess wrong (a wrapper that redirects output, a CI run that\n wants deterministic text). It is read from the environment of **whichever process builds the\n launch**, so set it in the right place:\n - foreground `cotal spawn`: your own shell, per spawn;\n - detached (`-d`): the **manager's** environment, because the manager builds the launch. Set it\n where you start the manager (`COTAL_CODEX_TUI=0 cotal up`) and it applies to every codex agent\n that manager supervises. Exporting it in the shell that runs `cotal spawn -d` does nothing.\n\n A detached agent gets the manager's pty, which *is* a terminal, so the default there is the TUI,\n which is what `cotal attach` streams.\n Once the TUI paints, the terminal belongs to Codex, so the host's own diagnostics move to\n `host.log` inside the agent's private home\n (`<workspace>/.cotal/codex/<space>-<name>-<hash>/host.log`; the handoff line prints the exact\n path, and `ls -t .cotal/codex/*/host.log` finds it after the fact). Attached, a failure is also\n reported on the terminal; detached, that report goes to the pty, so the file is the durable copy.\n- **Presence from events.** working/idle/waiting are derived from the app-server event stream;\n the model id is reported from the started thread.\n\n`--opt k=v` launch options render as codex `-c k=v` config overrides on the app-server child\n(top-level keys, scalar values; write TOML inline-table text yourself for nested values). The\nconnector's own defaults and selectors ride the same rail and yield to yours, except\n`mcp_servers`, which is how the agent reaches the mesh: the whole namespace is refused loud (at\nspawn, not at launch) rather than silently overridden.\n\n## Event plane\n\nA seat launched with `cotal spawn --events` publishes a structured account of what it did: run\nboundaries per turn, assistant text, reasoning, and the tool calls the model makes through Codex's\nfunction-call and custom-tool interfaces, each with its arguments, its end, and its result. That\ncovers the tools you watch a seat use, `shell` and `apply_patch` among them. The channel is\n`events.<owner>.<actor>`, named after the seat's principal, and the rules for it are the same on\nevery connector: see [connect-claude.md](connect-claude.md#event-plane) for the channel, the grant,\nand how to read it. Arming is `COTAL_EVENTS`, which the launcher sets for `--events` spawns; your own\n`codex` publishes nothing.\n\n```bash\ncotal spawn watcher --agent codex --events -d # armed, detached; read it with `cotal console`\n```\n\nEight things are specific to Codex and worth knowing before you read a stream:\n\n- **The thread's rollout file is the durable record.** The seat's\n rollout lives inside its own isolated `CODEX_HOME`, under\n `<workspace>/.cotal/codex/<space>-<name>-<hash>/sessions/<yyyy>/<mm>/<dd>/rollout-<stamp>-<thread>.jsonl`.\n Reading the file rather than the stream is what lets the seat resume a thread's stream where it\n stopped after its own process restarts, rather than reopening it from the top.\n- **A restarted app-server is a NEW thread, and its stream is a new one.** When the child dies and\n the seat brings up a replacement, Codex starts a fresh thread with a fresh rollout. The seat\n finishes the old one first, publishing what it had and closing any run left open, then begins\n publishing the new thread under its own write-ahead log. A reader sees one stream end and another\n begin, never one stream silently continuing under a different thread. If the new thread's file is\n slow to appear the order is the other way round: the seat spends its whole bounded look for the new\n file first, and the old stream ends when that look gives up, not at the moment of the restart. From\n the give-up on it publishes nothing until the new thread binds at a later turn boundary; it does not\n keep reporting the dead thread's activity in the meantime.\n- **The stream starts where the seat binds to the file.** `thread/start` writes nothing to disk; the\n file appears when the thread is primed. The seat binds to it then, and publishes from that point\n forward. If the file is slow to appear the seat says so in its log and looks again at each turn\n boundary, and whatever the thread wrote before the bind is not republished.\n- **Codex's built-in tools remain private to the host.** Web search, tool search and image generation\n record an end with no start, and nothing joins the two halves: the start-shaped record carries no\n call id and the end carries one. Rather than guess a pairing, the seat drops them, so those tool\n uses are absent from the stream while everything on the function-call path is present.\n- **Failed turns publish run errors.** Codex records a failure on\n the turn's own completion record, so a turn that hit a usage limit or an upstream error ends its\n run with `RUN_ERROR` carrying the code Codex reported.\n- **No user-authored text is published, ever.** Your prompts, the peer messages injected into the\n thread, and the developer instructions the persona supplies are all withheld. The events channel\n carries a different read ACL from the channel you typed into, so republishing your own words there\n would widen who can read them. Assistant text, reasoning and tool activity are unaffected.\n- **The seat waits out a broker outage at startup.** The plane publishes\n through the seat's mesh connection, so a seat armed while its broker was unreachable cannot start\n its emitter. It says so in its log, and rebuilds the emitter at the first turn boundary once the\n broker is there. A rebind DECLINES to publish two things, and they are one rule rather than two\n exceptions. It declines what the thread wrote while the seat was cut off. It also declines the\n turn whose own boundary triggered it: Codex writes a turn's first record before it announces that\n the turn started, and that announcement is what a rebind runs on, so the record is always behind\n whatever boundary the rebind takes, and a run is never opened from the middle of a turn. The first\n turn to start after the rebind is published in full. One case is different and is named here\n rather than left to be discovered: if the emitter had already been publishing this thread and\n then died, the seat's log carries its position, and the rebind CONTINUES that log rather than\n starting where it binds. An outage there costs the wait, not the content: everything the thread\n wrote while the plane was down, including whatever it wrote while the plane was already dead, is\n published once the plane is back. Two consequences are worth stating plainly, because both are\n easy to read past. A tool RESULT is published as the tool returned it, so anything a tool read on\n the seat's behalf, including messages it fetched from a channel with a narrower reader set, is in\n this stream; nothing redacts it or marks where it came from. And a backlog written while the\n plane was terminal is not discarded, it is delivered on recovery. Together those mean the readers\n of an events channel must be treated as at least as wide as every channel the seat's own tools\n can read. What the stream does not carry, here or on a live plane, is the session's own record of\n the user's words and the developer instructions. Neither of those two carriers is introduced by\n the boundary rule above and neither changes shape, but the rule is not confined to the seat whose\n emitter never started. It changes WHICH RECORDS reach the stream, on every armed seat. A bind\n announces where the stream starts and the emitter's setup then runs before its first read; what\n the thread appended inside that window used to land behind the cursor and be dropped, and it is\n published now. A whole turn can sit in there, tool results included, so the carrier described\n just above now covers a stretch of the session it previously lost. Nothing is sent twice in\n either case.\n\n And the reader set is a requirement rather than a guarantee, which is the last thing to say\n plainly. The grant does not enforce it, and it is worth being exact about what does. A spawn\n through the manager gives a seat publish rights on its own event channel and nothing else, and a\n spawn whose grant names a different agent's event channel is refused at the door. That fence is\n the manager's, it reads the concrete form and leaves a pattern such as `events.<owner>.>` to\n ordinary ACL authority, and a foreground `cotal spawn` on your own machine grants whatever you\n name because it mints from your own signing material. [connect-claude.md](connect-claude.md#event-plane)\n spells all three out. Who may READ a plane is minted separately and out of band either way, with\n `cotal actor grant` on a user-auth mesh and `cotal mint --profile agent --allow-subscribe` on a\n static one. So holding the events readers to at least the width of every channel the seat's tools\n can read is the operator's policy to keep, enforced by whoever mints those readers.\n- **Reasoning is published as its summary only.** Codex also stores an encrypted reasoning blob on\n every reasoning record; it is opaque, no reader can display it, and it is never put on the wire.\n\n## Sandbox autonomy\n\nA spawned Codex agent is woken by peer messages, which arrive when nobody is watching the\nterminal. The defaults follow from that, and all three are overridable per spawn with `--opt`.\n\n| Default | What it means |\n| --- | --- |\n| `approval_policy=\"never\"` | Never **ask** before running a command. Not \"refuse\": the agent runs its commands, it just does not stop to prompt. An interactive policy is refused loud rather than honored dishonestly, because a mesh-driven turn would block forever on a prompt nobody sees, and the alternative (auto-answering for you) nullifies the policy you asked for. |\n| `sandbox_mode=\"workspace-write\"` | Commands may read anywhere but write only inside the agent's workspace. This, not the prompt, is the part that is actually enforced; see below for the (real) exposure it leaves. |\n| `sandbox_workspace_write={network_access=true}` | Network **on** inside that sandbox. Codex's own default is off, which breaks installing a dependency, pushing a branch, or calling an API, with an error that reads like the task is impossible rather than the sandbox saying no. Applied only when the sandbox is actually `workspace-write`: tighten the mode and no network grant is emitted at all. |\n\nWhat the sandbox guarantees, stated literally: it **blocks out-of-workspace local filesystem\nwrites**. It does **not** block reads, exfiltration, or networked side effects.\n\nAll three of those are live with the defaults above, because a peer's message is a **remote input**\nthat can cause this agent to run commands. A confused or hostile peer can in principle get it to\nread a file elsewhere on your machine and send it; reach loopback or link-local services; or act\nthrough any credential it can read, which includes irreversible actions: a force-push, an API\ndelete, a deploy. Containing filesystem writes is therefore not the same as containing damage, and\nit should not be read that way. It is still worth keeping, because it is the one class this sandbox\ncan actually enforce.\n\nIf that exposure is wrong for a given agent, turn the network back off (below), tighten the mode,\nor run it under a separate OS user; the same point is repeated under [Limits](#limits) so it\nsurvives a skim. The spawn capability is the trust boundary for *who* may create an agent; the\nsandbox bounds one class of what it can then be talked into doing, not all of it.\n\nTune it per spawn:\n\n```bash\ncotal spawn --agent codex --opt sandbox_mode=read-only # tightest: no writes\ncotal spawn --agent codex --opt 'sandbox_workspace_write={network_access=false}' # contained, offline\ncotal spawn --agent codex --opt sandbox_mode=danger-full-access # no sandbox at all\n```\n\n`danger-full-access` is Codex's own name for it and means what it says: the agent may write\nanywhere your user account can. Codex documents that mode as intended only for environments that\nare already externally sandboxed (a container, a VM), not a workstation. On a laptop, prefer\ntightening the workspace over removing the sandbox.\n\n## Limits\n\n- **The sandbox blocks out-of-workspace filesystem writes, and only that.** It does not block\n reads, exfiltration, or networked side effects. With the default `workspace-write` + network on,\n a peer-driven turn can read anything your user account can (`~/.ssh`, `~/.aws`, `.env` files, the\n agent's own `auth.json`) and send it; reach loopback and link-local services; and act through any\n credential it can read, including irreversibly (a force-push, an API delete, a deploy). Only\n local writes outside the workspace are stopped, so this is not \"everything risky is reversible\"\n and not \"the only exposure is disclosure\". If that is wrong for a given agent, spawn it with\n `--opt 'sandbox_workspace_write={network_access=false}'` or `--opt sandbox_mode=read-only`, or\n run it as a separate OS user. See [Sandbox autonomy](#sandbox-autonomy).\n- **Not a boundary between agents on one machine.** The app-server listener and the tool\n endpoint are both loopback-bound and token-authenticated, which keeps out other OS users and\n anything off-box. It is not isolation between *managed agents*, which run as the same user and\n can therefore reach each other's tokens; a hostile agent on your workstation could drive\n another's Codex or speak as it on the mesh. Run mutually distrusted agents under separate OS\n users or separate machines.\n- **The TUI is local-only.** The app-server listener binds loopback and nothing else, so\n attaching Codex's UI to an agent on another machine needs your own SSH port-forward; there is\n no built-in remote attach. `cotal attach` (which streams the manager's pty) is the supported\n way to reach a detached agent.\n- **No session resume.** `cotal spawn --resume <id>` throws: a resumed codex thread comes up\n without its configured MCP servers, so the agent would be mute on the mesh.\n- **No tool-sharing.** `connectors.codex.mcpServers` is not implemented and throws if set.\n- **Experimental upstream surface.** `codex app-server` is labeled experimental by OpenAI (it\n is also what the Codex TUI itself runs on). The connector pins every protocol shape in one\n driver file and re-proves the contract with a gated live smoke (`COTAL_E2E_CODEX=1`).\n\n## See also\n\n- [Connectors](connectors.md): the feature matrix across all connectors\n- [Run a mesh](run-a-mesh.md) \xB7 [Define a team](define-a-team.md) \xB7 [Watch a mesh](watch-a-mesh.md)\n- [MCP tools](mcp-tools.md) \xB7 [Connect Claude Code](connect-claude.md) \xB7 [Connect OpenCode](connect-opencode.md)\n"
|
|
16212
|
+
"body": "# Connect Codex (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[OpenAI Codex](https://developers.openai.com/codex/) joins a Cotal mesh as a lateral peer: the\nsame `cotal_*` tool surface, the same message delivery and attention model as the other\nconnectors, plus mid-turn steering (previously pi-only): a directed peer message arriving\nmid-turn is **steered into the running turn** instead of waiting for it to end.\n\n**Beta** means the everyday path (spawn into the real Codex TUI, coordinate, watch) works; the\nspawn options that are not wired **fail loud** rather than degrade: resuming a session\n(`--resume`) and tool-sharing (`connectors.codex.mcpServers`). See [Limits](#limits).\n\n## Install\n\nThe connector ships with the CLI as a seeded extension (`@cotal-ai/connector-codex`): no\nseparate install step and no Codex-side plugin. You only need an authenticated `codex` binary\non your PATH (a ChatGPT-plan login or an `OPENAI_API_KEY`). If an older install is missing it,\n`cotal ext seed --repair` (or `cotal ext add @cotal-ai/connector-codex`) brings it in.\n\n**Don't install the `cotal` plugin Codex offers you.** Searching Codex's plugin list for \"cotal\"\nturns up a plugin named `cotal`, from the `cotal-mesh` marketplace. That is the **Claude Code**\nadapter, which appears there only because Codex reads the same plugin-marketplace format; it is\nnot this connector and installing it does not connect Codex to a mesh. Codex needs nothing\ninstalled on its side: the connector drives it from the outside, over `codex app-server`.\n\n**Codex version.** The connector drives `codex app-server` over its experimental v2 surface.\nMinimum **codex-cli 0.145.0**; tested against 0.145.0 and 0.146.0. An older binary authenticates fine but has\nno `--listen`/`--ws-auth` listener, so the launch fails at startup rather than misbehaving quietly:\ncheck with `codex --version` and upgrade (`npm i -g @openai/codex`) if a launch reports that the\napp-server exited before it started listening. The surface is explicitly experimental upstream, so\na later Codex release may change it and need a connector update. That is a break to report, not a\nsupport range we can promise ahead of it.\n\n## Spawn it\n\nSame launch grammar as any agent (see [run-a-mesh.md](run-a-mesh.md)):\n\n```bash\ncotal spawn --agent codex # foreground in this terminal\ncotal spawn reviewer --agent codex -d # detached via the manager; watch with `cotal attach`\nCOTAL_DEFAULT_AGENT=codex cotal spawn # make codex the default harness\n```\n\nOr set `agent: codex` in a team [manifest](manifest.md). Persona, role, and model come from the\nagent file as for any connector ([agent-files.md](agent-files.md)).\n\n## Choose a model\n\n```bash\ncotal models --agent codex # ids + reasoning-effort variants, via app-server model/list\ncotal spawn --agent codex --model gpt-5.6-sol --variant high\n```\n\nThe **variant** is Codex's reasoning effort (`minimal` | `low` | `medium` | `high` | `xhigh`).\nLike the `codex` CLI itself, the connector does not validate model ids or efforts locally. An\nunknown value fails at request time, server-side.\n\nModel and variant are published on presence, which is where `cotal roster` and the web dashboard's\n`model \xB7 variant` badge read them from. The variant appears only when you asked for one (via\n`--variant` or `variant:` in the agent file): there is no way to read the effort back off a running\nthread, so an unset variant is shown as absent rather than guessed at.\n\n## How it binds\n\nCodex has no in-process plugin runtime and its MCP client cannot wake an idle session, so the\nconnector runs Codex's own client/server split: a small **host process** embeds the mesh\nendpoint and drives a `codex app-server` thread over JSON-RPC (the same protocol the Codex TUI\nruns on). The app-server runs as an authenticated loopback **listener** rather than a private\npipe, which is what lets Codex's own TUI attach to the very thread the mesh is driving.\n\n- **Wake and steer.** An inbound batch starts a real turn (`turn/start`). A DIRECTED message\n (DM, anycast, @mention) arriving mid-turn is injected into the live turn (`turn/steer`);\n ambient channel chatter waits for the turn boundary so it can't derail work in flight.\n- **Native tools, one endpoint.** The host serves the shared `cotal_*` tools itself, on a\n bearer-authenticated loopback MCP endpoint (the token is passed by env name, so it never appears\n in the process table; see [Limits](#limits) for what that token does and does not protect). The model calls them like any tool and they\n execute against the host's single mesh endpoint: no sidecar process, no second identity. The\n app-server is the MCP client, so the tools work the same on a turn a peer message started and\n on one **you** typed into the TUI.\n- **Ready means on the mesh.** The host announces `ready` and hands the terminal to Codex only\n after the app-server, MCP surface, and mesh endpoint are all live (including the initial\n presence publish). If the broker cannot be reached, startup fails within 15 seconds with the\n broker address and latest connection error; it never opens an offline-looking TUI.\n- **At-least-once delivery.** A turn's surfaced messages are acked (by exact id) only when the\n turn completes. A failed turn retries with backoff, and an interrupted turn leaves the batch to\n redeliver. If the Codex app-server itself dies, the host restarts it in place (same mesh\n identity, credential, and durable) and re-drives the un-acked batch into the new thread; a\n crash *loop* (more than 3 in 2 minutes) is fatal rather than an endless respawn. (The shared\n bounded-inbox overflow rule applies: under extreme bursts an evicted in-flight id cannot\n redeliver.)\n- **Isolated, never written.** Each agent gets a private `CODEX_HOME` (one hashed directory\n per space+name under `.cotal/codex/`, rooted at the manager's workspace): your `~/.codex`\n config.toml, hooks, and MCP servers never load into a managed agent, and Codex's per-project\n trust records never touch your real config. Your `auth.json` is symlinked in (re-linked each\n launch), so ChatGPT-plan token refreshes never fork. Without an `auth.json` (or an\n `OPENAI_API_KEY`) the launch fails loud at thread start. Keyring-stored credentials are not\n wired through the isolated home; use the file store or the env key for managed agents. That\n symlink is why managed Codex agents are **POSIX-only** today: on Windows without Developer\n Mode the link fails, and the launch fails loud rather than copying `auth.json` (a copy would\n fork the token and break plan refreshes).\n- **Autonomy defaults.** Spawned agents run `approval_policy=never`,\n `sandbox_mode=workspace-write`, and `sandbox_workspace_write={network_access=true}`.\n See [Sandbox autonomy](#sandbox-autonomy) for what each one means and how to\n change it.\n- **It really is Codex.** `cotal spawn --agent codex` drops you into the actual Codex TUI,\n attached to the thread the mesh drives (`codex resume --remote`). Mesh turns render as they\n happen, and anything you type is a real user turn on that same thread with the `cotal_*` tools\n still available. In the foreground that is your terminal; detached it is the manager's pty,\n which is what `cotal attach` streams and drives. With no terminal at all (piped output,\n CI, a smoke) the host stays headless and prints an activity feed instead: the same peer either\n way, only the UI differs.\n **Which mode you get** is decided by whether *stdout* is a terminal, and `COTAL_CODEX_TUI=1|0`\n overrides that check when it would guess wrong (a wrapper that redirects output, a CI run that\n wants deterministic text). It is read from the environment of **whichever process builds the\n launch**, so set it in the right place:\n - foreground `cotal spawn`: your own shell, per spawn;\n - detached (`-d`): the **manager's** environment, because the manager builds the launch. Set it\n where you start the manager (`COTAL_CODEX_TUI=0 cotal up`) and it applies to every codex agent\n that manager supervises. Exporting it in the shell that runs `cotal spawn -d` does nothing.\n\n A detached agent gets the manager's pty, which *is* a terminal, so the default there is the TUI,\n which is what `cotal attach` streams.\n Once the TUI paints, the terminal belongs to Codex, so the host's own diagnostics move to\n `host.log` inside the agent's private home\n (`<workspace>/.cotal/codex/<space>-<name>-<hash>/host.log`; the handoff line prints the exact\n path, and `ls -t .cotal/codex/*/host.log` finds it after the fact). Attached, a failure is also\n reported on the terminal; detached, that report goes to the pty, so the file is the durable copy.\n- **Presence from events.** working/idle/waiting are derived from the app-server event stream;\n the model id is reported from the started thread.\n\n`--opt k=v` launch options render as codex `-c k=v` config overrides on the app-server child\n(top-level keys, scalar values; write TOML inline-table text yourself for nested values). The\nconnector's own defaults and selectors ride the same rail and yield to yours, except\n`mcp_servers`, which is how the agent reaches the mesh: the whole namespace is refused loud (at\nspawn, not at launch) rather than silently overridden.\n\n## Event plane\n\nA seat launched with `cotal spawn --events` publishes a structured account of what it did: run\nboundaries per turn, assistant text, reasoning, and the tool calls the model makes through Codex's\nfunction-call and custom-tool interfaces, each with its arguments, its end, and its result. That\ncovers the tools you watch a seat use, `shell` and `apply_patch` among them. The channel is\n`events.<owner>.<actor>`, named after the seat's principal, and the rules for it are the same on\nevery connector: see [connect-claude.md](connect-claude.md#event-plane) for the channel, the grant,\nand how to read it. Arming is `COTAL_EVENTS`, which the launcher sets for `--events` spawns; your own\n`codex` publishes nothing.\n\n```bash\ncotal spawn watcher --agent codex --events -d # armed, detached; read it with `cotal console`\n```\n\nEight things are specific to Codex and worth knowing before you read a stream:\n\n- **The thread's rollout file is the durable record.** The seat's\n rollout lives inside its own isolated `CODEX_HOME`, under\n `<workspace>/.cotal/codex/<space>-<name>-<hash>/sessions/<yyyy>/<mm>/<dd>/rollout-<stamp>-<thread>.jsonl`.\n Reading the file rather than the stream is what lets the seat resume a thread's stream where it\n stopped after its own process restarts, rather than reopening it from the top.\n- **A restarted app-server is a NEW thread, and its stream is a new one.** When the child dies and\n the seat brings up a replacement, Codex starts a fresh thread with a fresh rollout. The seat\n finishes the old one first, publishing what it had and closing any run left open, then begins\n publishing the new thread under its own write-ahead log. A reader sees one stream end and another\n begin, never one stream silently continuing under a different thread. If the new thread's file is\n slow to appear the order is the other way round: the seat spends its whole bounded look for the new\n file first, and the old stream ends when that look gives up, not at the moment of the restart. From\n the give-up on it publishes nothing until the new thread binds at a later turn boundary; it does not\n keep reporting the dead thread's activity in the meantime.\n- **The stream starts where the seat binds to the file.** `thread/start` writes nothing to disk; the\n file appears when the thread is primed. The seat binds to it then, and publishes from that point\n forward. If the file is slow to appear the seat says so in its log and looks again at each turn\n boundary, and whatever the thread wrote before the bind is not republished.\n- **Codex's built-in tools remain private to the host.** Web search, tool search and image generation\n record an end with no start, and nothing joins the two halves: the start-shaped record carries no\n call id and the end carries one. Rather than guess a pairing, the seat drops them, so those tool\n uses are absent from the stream while everything on the function-call path is present.\n- **Failed turns publish run errors.** Codex records a failure on\n the turn's own completion record, so a turn that hit a usage limit or an upstream error ends its\n run with `RUN_ERROR` carrying the code Codex reported.\n- **No user-authored text is published, ever.** Your prompts, the peer messages injected into the\n thread, and the developer instructions the persona supplies are all withheld. The events channel\n carries a different read ACL from the channel you typed into, so republishing your own words there\n would widen who can read them. Assistant text, reasoning and tool activity are unaffected.\n- **Recovery after a broker outage.** Initial mesh absence still\n fails the host's readiness gate within 15 seconds, so it never opens an offline-looking TUI. Once\n ready, the plane publishes through the seat's reconnecting mesh endpoint: an outage can stop an\n emitter, and the first turn boundary after reconnect rebuilds it. A rebind DECLINES to publish two\n things, and they are one rule rather than two exceptions. It declines what the thread wrote while\n the seat was cut off. It also declines the\n turn whose own boundary triggered it: Codex writes a turn's first record before it announces that\n the turn started, and that announcement is what a rebind runs on, so the record is always behind\n whatever boundary the rebind takes, and a run is never opened from the middle of a turn. The first\n turn to start after the rebind is published in full. One case is different and is named here\n rather than left to be discovered: if the emitter had already been publishing this thread and\n then died, the seat's log carries its position, and the rebind CONTINUES that log rather than\n starting where it binds. The rebind publishes the complete outage backlog once the plane is back,\n including everything the thread wrote after the previous emitter became terminal. Two consequences\n are worth stating plainly, because both are easy to read past. A tool RESULT is published as the tool returned it, so anything a tool read on\n the seat's behalf, including messages it fetched from a channel with a narrower reader set, is in\n this stream; nothing redacts it or marks where it came from. And a backlog written while the\n plane was terminal is not discarded, it is delivered on recovery. Together those mean the readers\n of an events channel must be treated as at least as wide as every channel the seat's own tools\n can read. What the stream does not carry, here or on a live plane, is the session's own record of\n the user's words and the developer instructions. Neither of those two carriers is introduced by\n the boundary rule above and neither changes shape, but the rule is not confined to the seat whose\n emitter never started. It changes WHICH RECORDS reach the stream, on every armed seat. A bind\n announces where the stream starts and the emitter's setup then runs before its first read; what\n the thread appended inside that window used to land behind the cursor and be dropped, and it is\n published now. A whole turn can sit in there, tool results included, so the carrier described\n just above now covers a stretch of the session it previously lost. Nothing is sent twice in\n either case.\n\n And the reader set is a requirement rather than a guarantee, which is the last thing to say\n plainly. The grant does not enforce it, and it is worth being exact about what does. A spawn\n through the manager gives a seat publish rights on its own event channel and nothing else, and a\n spawn whose grant names a different agent's event channel is refused at the door. That fence is\n the manager's, it reads the concrete form and leaves a pattern such as `events.<owner>.>` to\n ordinary ACL authority, and a foreground `cotal spawn` on your own machine grants whatever you\n name because it mints from your own signing material. [connect-claude.md](connect-claude.md#event-plane)\n spells all three out. Who may READ a plane is minted separately and out of band either way, with\n `cotal actor grant` on a user-auth mesh and `cotal mint --profile agent --allow-subscribe` on a\n static one. So holding the events readers to at least the width of every channel the seat's tools\n can read is the operator's policy to keep, enforced by whoever mints those readers.\n- **Reasoning is published as its summary only.** Codex also stores an encrypted reasoning blob on\n every reasoning record; it is opaque, no reader can display it, and it is never put on the wire.\n\n## Sandbox autonomy\n\nA spawned Codex agent is woken by peer messages, which arrive when nobody is watching the\nterminal. The defaults follow from that, and all three are overridable per spawn with `--opt`.\n\n| Default | What it means |\n| --- | --- |\n| `approval_policy=\"never\"` | Never **ask** before running a command. Not \"refuse\": the agent runs its commands, it just does not stop to prompt. An interactive policy is refused loud rather than honored dishonestly, because a mesh-driven turn would block forever on a prompt nobody sees, and the alternative (auto-answering for you) nullifies the policy you asked for. |\n| `sandbox_mode=\"workspace-write\"` | Commands may read anywhere but write only inside the agent's workspace. This, not the prompt, is the part that is actually enforced; see below for the (real) exposure it leaves. |\n| `sandbox_workspace_write={network_access=true}` | Network **on** inside that sandbox. Codex's own default is off, which breaks installing a dependency, pushing a branch, or calling an API, with an error that reads like the task is impossible rather than the sandbox saying no. Applied only when the sandbox is actually `workspace-write`: tighten the mode and no network grant is emitted at all. |\n\nWhat the sandbox guarantees, stated literally: it **blocks out-of-workspace local filesystem\nwrites**. It does **not** block reads, exfiltration, or networked side effects.\n\nAll three of those are live with the defaults above, because a peer's message is a **remote input**\nthat can cause this agent to run commands. A confused or hostile peer can in principle get it to\nread a file elsewhere on your machine and send it; reach loopback or link-local services; or act\nthrough any credential it can read, which includes irreversible actions: a force-push, an API\ndelete, a deploy. Containing filesystem writes is therefore not the same as containing damage, and\nit should not be read that way. It is still worth keeping, because it is the one class this sandbox\ncan actually enforce.\n\nIf that exposure is wrong for a given agent, turn the network back off (below), tighten the mode,\nor run it under a separate OS user; the same point is repeated under [Limits](#limits) so it\nsurvives a skim. The spawn capability is the trust boundary for *who* may create an agent; the\nsandbox bounds one class of what it can then be talked into doing, not all of it.\n\nTune it per spawn:\n\n```bash\ncotal spawn --agent codex --opt sandbox_mode=read-only # tightest: no writes\ncotal spawn --agent codex --opt 'sandbox_workspace_write={network_access=false}' # contained, offline\ncotal spawn --agent codex --opt sandbox_mode=danger-full-access # no sandbox at all\n```\n\n`danger-full-access` is Codex's own name for it and means what it says: the agent may write\nanywhere your user account can. Codex documents that mode as intended only for environments that\nare already externally sandboxed (a container, a VM), not a workstation. On a laptop, prefer\ntightening the workspace over removing the sandbox.\n\n## Limits\n\n- **The sandbox blocks out-of-workspace filesystem writes, and only that.** It does not block\n reads, exfiltration, or networked side effects. With the default `workspace-write` + network on,\n a peer-driven turn can read anything your user account can (`~/.ssh`, `~/.aws`, `.env` files, the\n agent's own `auth.json`) and send it; reach loopback and link-local services; and act through any\n credential it can read, including irreversibly (a force-push, an API delete, a deploy). Only\n local writes outside the workspace are stopped, so this is not \"everything risky is reversible\"\n and not \"the only exposure is disclosure\". If that is wrong for a given agent, spawn it with\n `--opt 'sandbox_workspace_write={network_access=false}'` or `--opt sandbox_mode=read-only`, or\n run it as a separate OS user. See [Sandbox autonomy](#sandbox-autonomy).\n- **Not a boundary between agents on one machine.** The app-server listener and the tool\n endpoint are both loopback-bound and token-authenticated, which keeps out other OS users and\n anything off-box. It is not isolation between *managed agents*, which run as the same user and\n can therefore reach each other's tokens; a hostile agent on your workstation could drive\n another's Codex or speak as it on the mesh. Run mutually distrusted agents under separate OS\n users or separate machines.\n- **The TUI is local-only.** The app-server listener binds loopback and nothing else, so\n attaching Codex's UI to an agent on another machine needs your own SSH port-forward; there is\n no built-in remote attach. `cotal attach` (which streams the manager's pty) is the supported\n way to reach a detached agent.\n- **No session resume.** `cotal spawn --resume <id>` throws: a resumed codex thread comes up\n without its configured MCP servers, so the agent would be mute on the mesh.\n- **No tool-sharing.** `connectors.codex.mcpServers` is not implemented and throws if set.\n- **Experimental upstream surface.** `codex app-server` is labeled experimental by OpenAI (it\n is also what the Codex TUI itself runs on). The connector pins every protocol shape in one\n driver file and re-proves the contract with a gated live smoke (`COTAL_E2E_CODEX=1`).\n\n## See also\n\n- [Connectors](connectors.md): the feature matrix across all connectors\n- [Run a mesh](run-a-mesh.md) \xB7 [Define a team](define-a-team.md) \xB7 [Watch a mesh](watch-a-mesh.md)\n- [MCP tools](mcp-tools.md) \xB7 [Connect Claude Code](connect-claude.md) \xB7 [Connect OpenCode](connect-opencode.md)\n"
|
|
16213
16213
|
},
|
|
16214
16214
|
{
|
|
16215
16215
|
"slug": "connect-hermes",
|
package/dist/standalone.js
CHANGED
|
@@ -56070,7 +56070,7 @@ config(en_default());
|
|
|
56070
56070
|
|
|
56071
56071
|
// ../connector-core/dist/docs-bundle.generated.js
|
|
56072
56072
|
var DOCS_BUNDLE = {
|
|
56073
|
-
"version": "0.33.
|
|
56073
|
+
"version": "0.33.9",
|
|
56074
56074
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
|
|
56075
56075
|
"pages": [
|
|
56076
56076
|
{
|
|
@@ -56162,7 +56162,7 @@ var DOCS_BUNDLE = {
|
|
|
56162
56162
|
"title": "Connect Codex (beta)",
|
|
56163
56163
|
"kind": "Guide (informative)",
|
|
56164
56164
|
"summary": "OpenAI Codex joins a Cotal mesh as a lateral peer: the same cotal tool surface, the same message delivery and attention model as the other connectors, plus mid-turn steering (previously pi-only): a\u2026",
|
|
56165
|
-
"body": "# Connect Codex (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[OpenAI Codex](https://developers.openai.com/codex/) joins a Cotal mesh as a lateral peer: the\nsame `cotal_*` tool surface, the same message delivery and attention model as the other\nconnectors, plus mid-turn steering (previously pi-only): a directed peer message arriving\nmid-turn is **steered into the running turn** instead of waiting for it to end.\n\n**Beta** means the everyday path (spawn into the real Codex TUI, coordinate, watch) works; the\nspawn options that are not wired **fail loud** rather than degrade: resuming a session\n(`--resume`) and tool-sharing (`connectors.codex.mcpServers`). See [Limits](#limits).\n\n## Install\n\nThe connector ships with the CLI as a seeded extension (`@cotal-ai/connector-codex`): no\nseparate install step and no Codex-side plugin. You only need an authenticated `codex` binary\non your PATH (a ChatGPT-plan login or an `OPENAI_API_KEY`). If an older install is missing it,\n`cotal ext seed --repair` (or `cotal ext add @cotal-ai/connector-codex`) brings it in.\n\n**Don't install the `cotal` plugin Codex offers you.** Searching Codex's plugin list for \"cotal\"\nturns up a plugin named `cotal`, from the `cotal-mesh` marketplace. That is the **Claude Code**\nadapter, which appears there only because Codex reads the same plugin-marketplace format; it is\nnot this connector and installing it does not connect Codex to a mesh. Codex needs nothing\ninstalled on its side: the connector drives it from the outside, over `codex app-server`.\n\n**Codex version.** The connector drives `codex app-server` over its experimental v2 surface.\nMinimum **codex-cli 0.145.0**; tested against 0.145.0 and 0.146.0. An older binary authenticates fine but has\nno `--listen`/`--ws-auth` listener, so the launch fails at startup rather than misbehaving quietly:\ncheck with `codex --version` and upgrade (`npm i -g @openai/codex`) if a launch reports that the\napp-server exited before it started listening. The surface is explicitly experimental upstream, so\na later Codex release may change it and need a connector update. That is a break to report, not a\nsupport range we can promise ahead of it.\n\n## Spawn it\n\nSame launch grammar as any agent (see [run-a-mesh.md](run-a-mesh.md)):\n\n```bash\ncotal spawn --agent codex # foreground in this terminal\ncotal spawn reviewer --agent codex -d # detached via the manager; watch with `cotal attach`\nCOTAL_DEFAULT_AGENT=codex cotal spawn # make codex the default harness\n```\n\nOr set `agent: codex` in a team [manifest](manifest.md). Persona, role, and model come from the\nagent file as for any connector ([agent-files.md](agent-files.md)).\n\n## Choose a model\n\n```bash\ncotal models --agent codex # ids + reasoning-effort variants, via app-server model/list\ncotal spawn --agent codex --model gpt-5.6-sol --variant high\n```\n\nThe **variant** is Codex's reasoning effort (`minimal` | `low` | `medium` | `high` | `xhigh`).\nLike the `codex` CLI itself, the connector does not validate model ids or efforts locally. An\nunknown value fails at request time, server-side.\n\nModel and variant are published on presence, which is where `cotal roster` and the web dashboard's\n`model \xB7 variant` badge read them from. The variant appears only when you asked for one (via\n`--variant` or `variant:` in the agent file): there is no way to read the effort back off a running\nthread, so an unset variant is shown as absent rather than guessed at.\n\n## How it binds\n\nCodex has no in-process plugin runtime and its MCP client cannot wake an idle session, so the\nconnector runs Codex's own client/server split: a small **host process** embeds the mesh\nendpoint and drives a `codex app-server` thread over JSON-RPC (the same protocol the Codex TUI\nruns on). The app-server runs as an authenticated loopback **listener** rather than a private\npipe, which is what lets Codex's own TUI attach to the very thread the mesh is driving.\n\n- **Wake and steer.** An inbound batch starts a real turn (`turn/start`). A DIRECTED message\n (DM, anycast, @mention) arriving mid-turn is injected into the live turn (`turn/steer`);\n ambient channel chatter waits for the turn boundary so it can't derail work in flight.\n- **Native tools, one endpoint.** The host serves the shared `cotal_*` tools itself, on a\n bearer-authenticated loopback MCP endpoint (the token is passed by env name, so it never appears\n in the process table; see [Limits](#limits) for what that token does and does not protect). The model calls them like any tool and they\n execute against the host's single mesh endpoint: no sidecar process, no second identity. The\n app-server is the MCP client, so the tools work the same on a turn a peer message started and\n on one **you** typed into the TUI.\n- **Ready means on the mesh.** The host announces `ready` and hands the terminal to Codex only\n after the app-server, MCP surface, and mesh endpoint are all live (including the initial\n presence publish). If the broker cannot be reached, startup fails within 15 seconds with the\n broker address and latest connection error; it never opens an offline-looking TUI.\n- **At-least-once delivery.** A turn's surfaced messages are acked (by exact id) only when the\n turn completes. A failed turn retries with backoff, and an interrupted turn leaves the batch to\n redeliver. If the Codex app-server itself dies, the host restarts it in place (same mesh\n identity, credential, and durable) and re-drives the un-acked batch into the new thread; a\n crash *loop* (more than 3 in 2 minutes) is fatal rather than an endless respawn. (The shared\n bounded-inbox overflow rule applies: under extreme bursts an evicted in-flight id cannot\n redeliver.)\n- **Isolated, never written.** Each agent gets a private `CODEX_HOME` (one hashed directory\n per space+name under `.cotal/codex/`, rooted at the manager's workspace): your `~/.codex`\n config.toml, hooks, and MCP servers never load into a managed agent, and Codex's per-project\n trust records never touch your real config. Your `auth.json` is symlinked in (re-linked each\n launch), so ChatGPT-plan token refreshes never fork. Without an `auth.json` (or an\n `OPENAI_API_KEY`) the launch fails loud at thread start. Keyring-stored credentials are not\n wired through the isolated home; use the file store or the env key for managed agents. That\n symlink is why managed Codex agents are **POSIX-only** today: on Windows without Developer\n Mode the link fails, and the launch fails loud rather than copying `auth.json` (a copy would\n fork the token and break plan refreshes).\n- **Autonomy defaults.** Spawned agents run `approval_policy=never`,\n `sandbox_mode=workspace-write`, and `sandbox_workspace_write={network_access=true}`.\n See [Sandbox autonomy](#sandbox-autonomy) for what each one means and how to\n change it.\n- **It really is Codex.** `cotal spawn --agent codex` drops you into the actual Codex TUI,\n attached to the thread the mesh drives (`codex resume --remote`). Mesh turns render as they\n happen, and anything you type is a real user turn on that same thread with the `cotal_*` tools\n still available. In the foreground that is your terminal; detached it is the manager's pty,\n which is what `cotal attach` streams and drives. With no terminal at all (piped output,\n CI, a smoke) the host stays headless and prints an activity feed instead: the same peer either\n way, only the UI differs.\n **Which mode you get** is decided by whether *stdout* is a terminal, and `COTAL_CODEX_TUI=1|0`\n overrides that check when it would guess wrong (a wrapper that redirects output, a CI run that\n wants deterministic text). It is read from the environment of **whichever process builds the\n launch**, so set it in the right place:\n - foreground `cotal spawn`: your own shell, per spawn;\n - detached (`-d`): the **manager's** environment, because the manager builds the launch. Set it\n where you start the manager (`COTAL_CODEX_TUI=0 cotal up`) and it applies to every codex agent\n that manager supervises. Exporting it in the shell that runs `cotal spawn -d` does nothing.\n\n A detached agent gets the manager's pty, which *is* a terminal, so the default there is the TUI,\n which is what `cotal attach` streams.\n Once the TUI paints, the terminal belongs to Codex, so the host's own diagnostics move to\n `host.log` inside the agent's private home\n (`<workspace>/.cotal/codex/<space>-<name>-<hash>/host.log`; the handoff line prints the exact\n path, and `ls -t .cotal/codex/*/host.log` finds it after the fact). Attached, a failure is also\n reported on the terminal; detached, that report goes to the pty, so the file is the durable copy.\n- **Presence from events.** working/idle/waiting are derived from the app-server event stream;\n the model id is reported from the started thread.\n\n`--opt k=v` launch options render as codex `-c k=v` config overrides on the app-server child\n(top-level keys, scalar values; write TOML inline-table text yourself for nested values). The\nconnector's own defaults and selectors ride the same rail and yield to yours, except\n`mcp_servers`, which is how the agent reaches the mesh: the whole namespace is refused loud (at\nspawn, not at launch) rather than silently overridden.\n\n## Event plane\n\nA seat launched with `cotal spawn --events` publishes a structured account of what it did: run\nboundaries per turn, assistant text, reasoning, and the tool calls the model makes through Codex's\nfunction-call and custom-tool interfaces, each with its arguments, its end, and its result. That\ncovers the tools you watch a seat use, `shell` and `apply_patch` among them. The channel is\n`events.<owner>.<actor>`, named after the seat's principal, and the rules for it are the same on\nevery connector: see [connect-claude.md](connect-claude.md#event-plane) for the channel, the grant,\nand how to read it. Arming is `COTAL_EVENTS`, which the launcher sets for `--events` spawns; your own\n`codex` publishes nothing.\n\n```bash\ncotal spawn watcher --agent codex --events -d # armed, detached; read it with `cotal console`\n```\n\nEight things are specific to Codex and worth knowing before you read a stream:\n\n- **The thread's rollout file is the durable record.** The seat's\n rollout lives inside its own isolated `CODEX_HOME`, under\n `<workspace>/.cotal/codex/<space>-<name>-<hash>/sessions/<yyyy>/<mm>/<dd>/rollout-<stamp>-<thread>.jsonl`.\n Reading the file rather than the stream is what lets the seat resume a thread's stream where it\n stopped after its own process restarts, rather than reopening it from the top.\n- **A restarted app-server is a NEW thread, and its stream is a new one.** When the child dies and\n the seat brings up a replacement, Codex starts a fresh thread with a fresh rollout. The seat\n finishes the old one first, publishing what it had and closing any run left open, then begins\n publishing the new thread under its own write-ahead log. A reader sees one stream end and another\n begin, never one stream silently continuing under a different thread. If the new thread's file is\n slow to appear the order is the other way round: the seat spends its whole bounded look for the new\n file first, and the old stream ends when that look gives up, not at the moment of the restart. From\n the give-up on it publishes nothing until the new thread binds at a later turn boundary; it does not\n keep reporting the dead thread's activity in the meantime.\n- **The stream starts where the seat binds to the file.** `thread/start` writes nothing to disk; the\n file appears when the thread is primed. The seat binds to it then, and publishes from that point\n forward. If the file is slow to appear the seat says so in its log and looks again at each turn\n boundary, and whatever the thread wrote before the bind is not republished.\n- **Codex's built-in tools remain private to the host.** Web search, tool search and image generation\n record an end with no start, and nothing joins the two halves: the start-shaped record carries no\n call id and the end carries one. Rather than guess a pairing, the seat drops them, so those tool\n uses are absent from the stream while everything on the function-call path is present.\n- **Failed turns publish run errors.** Codex records a failure on\n the turn's own completion record, so a turn that hit a usage limit or an upstream error ends its\n run with `RUN_ERROR` carrying the code Codex reported.\n- **No user-authored text is published, ever.** Your prompts, the peer messages injected into the\n thread, and the developer instructions the persona supplies are all withheld. The events channel\n carries a different read ACL from the channel you typed into, so republishing your own words there\n would widen who can read them. Assistant text, reasoning and tool activity are unaffected.\n- **The seat waits out a broker outage at startup.** The plane publishes\n through the seat's mesh connection, so a seat armed while its broker was unreachable cannot start\n its emitter. It says so in its log, and rebuilds the emitter at the first turn boundary once the\n broker is there. A rebind DECLINES to publish two things, and they are one rule rather than two\n exceptions. It declines what the thread wrote while the seat was cut off. It also declines the\n turn whose own boundary triggered it: Codex writes a turn's first record before it announces that\n the turn started, and that announcement is what a rebind runs on, so the record is always behind\n whatever boundary the rebind takes, and a run is never opened from the middle of a turn. The first\n turn to start after the rebind is published in full. One case is different and is named here\n rather than left to be discovered: if the emitter had already been publishing this thread and\n then died, the seat's log carries its position, and the rebind CONTINUES that log rather than\n starting where it binds. An outage there costs the wait, not the content: everything the thread\n wrote while the plane was down, including whatever it wrote while the plane was already dead, is\n published once the plane is back. Two consequences are worth stating plainly, because both are\n easy to read past. A tool RESULT is published as the tool returned it, so anything a tool read on\n the seat's behalf, including messages it fetched from a channel with a narrower reader set, is in\n this stream; nothing redacts it or marks where it came from. And a backlog written while the\n plane was terminal is not discarded, it is delivered on recovery. Together those mean the readers\n of an events channel must be treated as at least as wide as every channel the seat's own tools\n can read. What the stream does not carry, here or on a live plane, is the session's own record of\n the user's words and the developer instructions. Neither of those two carriers is introduced by\n the boundary rule above and neither changes shape, but the rule is not confined to the seat whose\n emitter never started. It changes WHICH RECORDS reach the stream, on every armed seat. A bind\n announces where the stream starts and the emitter's setup then runs before its first read; what\n the thread appended inside that window used to land behind the cursor and be dropped, and it is\n published now. A whole turn can sit in there, tool results included, so the carrier described\n just above now covers a stretch of the session it previously lost. Nothing is sent twice in\n either case.\n\n And the reader set is a requirement rather than a guarantee, which is the last thing to say\n plainly. The grant does not enforce it, and it is worth being exact about what does. A spawn\n through the manager gives a seat publish rights on its own event channel and nothing else, and a\n spawn whose grant names a different agent's event channel is refused at the door. That fence is\n the manager's, it reads the concrete form and leaves a pattern such as `events.<owner>.>` to\n ordinary ACL authority, and a foreground `cotal spawn` on your own machine grants whatever you\n name because it mints from your own signing material. [connect-claude.md](connect-claude.md#event-plane)\n spells all three out. Who may READ a plane is minted separately and out of band either way, with\n `cotal actor grant` on a user-auth mesh and `cotal mint --profile agent --allow-subscribe` on a\n static one. So holding the events readers to at least the width of every channel the seat's tools\n can read is the operator's policy to keep, enforced by whoever mints those readers.\n- **Reasoning is published as its summary only.** Codex also stores an encrypted reasoning blob on\n every reasoning record; it is opaque, no reader can display it, and it is never put on the wire.\n\n## Sandbox autonomy\n\nA spawned Codex agent is woken by peer messages, which arrive when nobody is watching the\nterminal. The defaults follow from that, and all three are overridable per spawn with `--opt`.\n\n| Default | What it means |\n| --- | --- |\n| `approval_policy=\"never\"` | Never **ask** before running a command. Not \"refuse\": the agent runs its commands, it just does not stop to prompt. An interactive policy is refused loud rather than honored dishonestly, because a mesh-driven turn would block forever on a prompt nobody sees, and the alternative (auto-answering for you) nullifies the policy you asked for. |\n| `sandbox_mode=\"workspace-write\"` | Commands may read anywhere but write only inside the agent's workspace. This, not the prompt, is the part that is actually enforced; see below for the (real) exposure it leaves. |\n| `sandbox_workspace_write={network_access=true}` | Network **on** inside that sandbox. Codex's own default is off, which breaks installing a dependency, pushing a branch, or calling an API, with an error that reads like the task is impossible rather than the sandbox saying no. Applied only when the sandbox is actually `workspace-write`: tighten the mode and no network grant is emitted at all. |\n\nWhat the sandbox guarantees, stated literally: it **blocks out-of-workspace local filesystem\nwrites**. It does **not** block reads, exfiltration, or networked side effects.\n\nAll three of those are live with the defaults above, because a peer's message is a **remote input**\nthat can cause this agent to run commands. A confused or hostile peer can in principle get it to\nread a file elsewhere on your machine and send it; reach loopback or link-local services; or act\nthrough any credential it can read, which includes irreversible actions: a force-push, an API\ndelete, a deploy. Containing filesystem writes is therefore not the same as containing damage, and\nit should not be read that way. It is still worth keeping, because it is the one class this sandbox\ncan actually enforce.\n\nIf that exposure is wrong for a given agent, turn the network back off (below), tighten the mode,\nor run it under a separate OS user; the same point is repeated under [Limits](#limits) so it\nsurvives a skim. The spawn capability is the trust boundary for *who* may create an agent; the\nsandbox bounds one class of what it can then be talked into doing, not all of it.\n\nTune it per spawn:\n\n```bash\ncotal spawn --agent codex --opt sandbox_mode=read-only # tightest: no writes\ncotal spawn --agent codex --opt 'sandbox_workspace_write={network_access=false}' # contained, offline\ncotal spawn --agent codex --opt sandbox_mode=danger-full-access # no sandbox at all\n```\n\n`danger-full-access` is Codex's own name for it and means what it says: the agent may write\nanywhere your user account can. Codex documents that mode as intended only for environments that\nare already externally sandboxed (a container, a VM), not a workstation. On a laptop, prefer\ntightening the workspace over removing the sandbox.\n\n## Limits\n\n- **The sandbox blocks out-of-workspace filesystem writes, and only that.** It does not block\n reads, exfiltration, or networked side effects. With the default `workspace-write` + network on,\n a peer-driven turn can read anything your user account can (`~/.ssh`, `~/.aws`, `.env` files, the\n agent's own `auth.json`) and send it; reach loopback and link-local services; and act through any\n credential it can read, including irreversibly (a force-push, an API delete, a deploy). Only\n local writes outside the workspace are stopped, so this is not \"everything risky is reversible\"\n and not \"the only exposure is disclosure\". If that is wrong for a given agent, spawn it with\n `--opt 'sandbox_workspace_write={network_access=false}'` or `--opt sandbox_mode=read-only`, or\n run it as a separate OS user. See [Sandbox autonomy](#sandbox-autonomy).\n- **Not a boundary between agents on one machine.** The app-server listener and the tool\n endpoint are both loopback-bound and token-authenticated, which keeps out other OS users and\n anything off-box. It is not isolation between *managed agents*, which run as the same user and\n can therefore reach each other's tokens; a hostile agent on your workstation could drive\n another's Codex or speak as it on the mesh. Run mutually distrusted agents under separate OS\n users or separate machines.\n- **The TUI is local-only.** The app-server listener binds loopback and nothing else, so\n attaching Codex's UI to an agent on another machine needs your own SSH port-forward; there is\n no built-in remote attach. `cotal attach` (which streams the manager's pty) is the supported\n way to reach a detached agent.\n- **No session resume.** `cotal spawn --resume <id>` throws: a resumed codex thread comes up\n without its configured MCP servers, so the agent would be mute on the mesh.\n- **No tool-sharing.** `connectors.codex.mcpServers` is not implemented and throws if set.\n- **Experimental upstream surface.** `codex app-server` is labeled experimental by OpenAI (it\n is also what the Codex TUI itself runs on). The connector pins every protocol shape in one\n driver file and re-proves the contract with a gated live smoke (`COTAL_E2E_CODEX=1`).\n\n## See also\n\n- [Connectors](connectors.md): the feature matrix across all connectors\n- [Run a mesh](run-a-mesh.md) \xB7 [Define a team](define-a-team.md) \xB7 [Watch a mesh](watch-a-mesh.md)\n- [MCP tools](mcp-tools.md) \xB7 [Connect Claude Code](connect-claude.md) \xB7 [Connect OpenCode](connect-opencode.md)\n"
|
|
56165
|
+
"body": "# Connect Codex (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[OpenAI Codex](https://developers.openai.com/codex/) joins a Cotal mesh as a lateral peer: the\nsame `cotal_*` tool surface, the same message delivery and attention model as the other\nconnectors, plus mid-turn steering (previously pi-only): a directed peer message arriving\nmid-turn is **steered into the running turn** instead of waiting for it to end.\n\n**Beta** means the everyday path (spawn into the real Codex TUI, coordinate, watch) works; the\nspawn options that are not wired **fail loud** rather than degrade: resuming a session\n(`--resume`) and tool-sharing (`connectors.codex.mcpServers`). See [Limits](#limits).\n\n## Install\n\nThe connector ships with the CLI as a seeded extension (`@cotal-ai/connector-codex`): no\nseparate install step and no Codex-side plugin. You only need an authenticated `codex` binary\non your PATH (a ChatGPT-plan login or an `OPENAI_API_KEY`). If an older install is missing it,\n`cotal ext seed --repair` (or `cotal ext add @cotal-ai/connector-codex`) brings it in.\n\n**Don't install the `cotal` plugin Codex offers you.** Searching Codex's plugin list for \"cotal\"\nturns up a plugin named `cotal`, from the `cotal-mesh` marketplace. That is the **Claude Code**\nadapter, which appears there only because Codex reads the same plugin-marketplace format; it is\nnot this connector and installing it does not connect Codex to a mesh. Codex needs nothing\ninstalled on its side: the connector drives it from the outside, over `codex app-server`.\n\n**Codex version.** The connector drives `codex app-server` over its experimental v2 surface.\nMinimum **codex-cli 0.145.0**; tested against 0.145.0 and 0.146.0. An older binary authenticates fine but has\nno `--listen`/`--ws-auth` listener, so the launch fails at startup rather than misbehaving quietly:\ncheck with `codex --version` and upgrade (`npm i -g @openai/codex`) if a launch reports that the\napp-server exited before it started listening. The surface is explicitly experimental upstream, so\na later Codex release may change it and need a connector update. That is a break to report, not a\nsupport range we can promise ahead of it.\n\n## Spawn it\n\nSame launch grammar as any agent (see [run-a-mesh.md](run-a-mesh.md)):\n\n```bash\ncotal spawn --agent codex # foreground in this terminal\ncotal spawn reviewer --agent codex -d # detached via the manager; watch with `cotal attach`\nCOTAL_DEFAULT_AGENT=codex cotal spawn # make codex the default harness\n```\n\nOr set `agent: codex` in a team [manifest](manifest.md). Persona, role, and model come from the\nagent file as for any connector ([agent-files.md](agent-files.md)).\n\n## Choose a model\n\n```bash\ncotal models --agent codex # ids + reasoning-effort variants, via app-server model/list\ncotal spawn --agent codex --model gpt-5.6-sol --variant high\n```\n\nThe **variant** is Codex's reasoning effort (`minimal` | `low` | `medium` | `high` | `xhigh`).\nLike the `codex` CLI itself, the connector does not validate model ids or efforts locally. An\nunknown value fails at request time, server-side.\n\nModel and variant are published on presence, which is where `cotal roster` and the web dashboard's\n`model \xB7 variant` badge read them from. The variant appears only when you asked for one (via\n`--variant` or `variant:` in the agent file): there is no way to read the effort back off a running\nthread, so an unset variant is shown as absent rather than guessed at.\n\n## How it binds\n\nCodex has no in-process plugin runtime and its MCP client cannot wake an idle session, so the\nconnector runs Codex's own client/server split: a small **host process** embeds the mesh\nendpoint and drives a `codex app-server` thread over JSON-RPC (the same protocol the Codex TUI\nruns on). The app-server runs as an authenticated loopback **listener** rather than a private\npipe, which is what lets Codex's own TUI attach to the very thread the mesh is driving.\n\n- **Wake and steer.** An inbound batch starts a real turn (`turn/start`). A DIRECTED message\n (DM, anycast, @mention) arriving mid-turn is injected into the live turn (`turn/steer`);\n ambient channel chatter waits for the turn boundary so it can't derail work in flight.\n- **Native tools, one endpoint.** The host serves the shared `cotal_*` tools itself, on a\n bearer-authenticated loopback MCP endpoint (the token is passed by env name, so it never appears\n in the process table; see [Limits](#limits) for what that token does and does not protect). The model calls them like any tool and they\n execute against the host's single mesh endpoint: no sidecar process, no second identity. The\n app-server is the MCP client, so the tools work the same on a turn a peer message started and\n on one **you** typed into the TUI.\n- **Ready means on the mesh.** The host announces `ready` and hands the terminal to Codex only\n after the app-server, MCP surface, and mesh endpoint are all live (including the initial\n presence publish). If the broker cannot be reached, startup fails within 15 seconds with the\n broker address and latest connection error; it never opens an offline-looking TUI.\n- **At-least-once delivery.** A turn's surfaced messages are acked (by exact id) only when the\n turn completes. A failed turn retries with backoff, and an interrupted turn leaves the batch to\n redeliver. If the Codex app-server itself dies, the host restarts it in place (same mesh\n identity, credential, and durable) and re-drives the un-acked batch into the new thread; a\n crash *loop* (more than 3 in 2 minutes) is fatal rather than an endless respawn. (The shared\n bounded-inbox overflow rule applies: under extreme bursts an evicted in-flight id cannot\n redeliver.)\n- **Isolated, never written.** Each agent gets a private `CODEX_HOME` (one hashed directory\n per space+name under `.cotal/codex/`, rooted at the manager's workspace): your `~/.codex`\n config.toml, hooks, and MCP servers never load into a managed agent, and Codex's per-project\n trust records never touch your real config. Your `auth.json` is symlinked in (re-linked each\n launch), so ChatGPT-plan token refreshes never fork. Without an `auth.json` (or an\n `OPENAI_API_KEY`) the launch fails loud at thread start. Keyring-stored credentials are not\n wired through the isolated home; use the file store or the env key for managed agents. That\n symlink is why managed Codex agents are **POSIX-only** today: on Windows without Developer\n Mode the link fails, and the launch fails loud rather than copying `auth.json` (a copy would\n fork the token and break plan refreshes).\n- **Autonomy defaults.** Spawned agents run `approval_policy=never`,\n `sandbox_mode=workspace-write`, and `sandbox_workspace_write={network_access=true}`.\n See [Sandbox autonomy](#sandbox-autonomy) for what each one means and how to\n change it.\n- **It really is Codex.** `cotal spawn --agent codex` drops you into the actual Codex TUI,\n attached to the thread the mesh drives (`codex resume --remote`). Mesh turns render as they\n happen, and anything you type is a real user turn on that same thread with the `cotal_*` tools\n still available. In the foreground that is your terminal; detached it is the manager's pty,\n which is what `cotal attach` streams and drives. With no terminal at all (piped output,\n CI, a smoke) the host stays headless and prints an activity feed instead: the same peer either\n way, only the UI differs.\n **Which mode you get** is decided by whether *stdout* is a terminal, and `COTAL_CODEX_TUI=1|0`\n overrides that check when it would guess wrong (a wrapper that redirects output, a CI run that\n wants deterministic text). It is read from the environment of **whichever process builds the\n launch**, so set it in the right place:\n - foreground `cotal spawn`: your own shell, per spawn;\n - detached (`-d`): the **manager's** environment, because the manager builds the launch. Set it\n where you start the manager (`COTAL_CODEX_TUI=0 cotal up`) and it applies to every codex agent\n that manager supervises. Exporting it in the shell that runs `cotal spawn -d` does nothing.\n\n A detached agent gets the manager's pty, which *is* a terminal, so the default there is the TUI,\n which is what `cotal attach` streams.\n Once the TUI paints, the terminal belongs to Codex, so the host's own diagnostics move to\n `host.log` inside the agent's private home\n (`<workspace>/.cotal/codex/<space>-<name>-<hash>/host.log`; the handoff line prints the exact\n path, and `ls -t .cotal/codex/*/host.log` finds it after the fact). Attached, a failure is also\n reported on the terminal; detached, that report goes to the pty, so the file is the durable copy.\n- **Presence from events.** working/idle/waiting are derived from the app-server event stream;\n the model id is reported from the started thread.\n\n`--opt k=v` launch options render as codex `-c k=v` config overrides on the app-server child\n(top-level keys, scalar values; write TOML inline-table text yourself for nested values). The\nconnector's own defaults and selectors ride the same rail and yield to yours, except\n`mcp_servers`, which is how the agent reaches the mesh: the whole namespace is refused loud (at\nspawn, not at launch) rather than silently overridden.\n\n## Event plane\n\nA seat launched with `cotal spawn --events` publishes a structured account of what it did: run\nboundaries per turn, assistant text, reasoning, and the tool calls the model makes through Codex's\nfunction-call and custom-tool interfaces, each with its arguments, its end, and its result. That\ncovers the tools you watch a seat use, `shell` and `apply_patch` among them. The channel is\n`events.<owner>.<actor>`, named after the seat's principal, and the rules for it are the same on\nevery connector: see [connect-claude.md](connect-claude.md#event-plane) for the channel, the grant,\nand how to read it. Arming is `COTAL_EVENTS`, which the launcher sets for `--events` spawns; your own\n`codex` publishes nothing.\n\n```bash\ncotal spawn watcher --agent codex --events -d # armed, detached; read it with `cotal console`\n```\n\nEight things are specific to Codex and worth knowing before you read a stream:\n\n- **The thread's rollout file is the durable record.** The seat's\n rollout lives inside its own isolated `CODEX_HOME`, under\n `<workspace>/.cotal/codex/<space>-<name>-<hash>/sessions/<yyyy>/<mm>/<dd>/rollout-<stamp>-<thread>.jsonl`.\n Reading the file rather than the stream is what lets the seat resume a thread's stream where it\n stopped after its own process restarts, rather than reopening it from the top.\n- **A restarted app-server is a NEW thread, and its stream is a new one.** When the child dies and\n the seat brings up a replacement, Codex starts a fresh thread with a fresh rollout. The seat\n finishes the old one first, publishing what it had and closing any run left open, then begins\n publishing the new thread under its own write-ahead log. A reader sees one stream end and another\n begin, never one stream silently continuing under a different thread. If the new thread's file is\n slow to appear the order is the other way round: the seat spends its whole bounded look for the new\n file first, and the old stream ends when that look gives up, not at the moment of the restart. From\n the give-up on it publishes nothing until the new thread binds at a later turn boundary; it does not\n keep reporting the dead thread's activity in the meantime.\n- **The stream starts where the seat binds to the file.** `thread/start` writes nothing to disk; the\n file appears when the thread is primed. The seat binds to it then, and publishes from that point\n forward. If the file is slow to appear the seat says so in its log and looks again at each turn\n boundary, and whatever the thread wrote before the bind is not republished.\n- **Codex's built-in tools remain private to the host.** Web search, tool search and image generation\n record an end with no start, and nothing joins the two halves: the start-shaped record carries no\n call id and the end carries one. Rather than guess a pairing, the seat drops them, so those tool\n uses are absent from the stream while everything on the function-call path is present.\n- **Failed turns publish run errors.** Codex records a failure on\n the turn's own completion record, so a turn that hit a usage limit or an upstream error ends its\n run with `RUN_ERROR` carrying the code Codex reported.\n- **No user-authored text is published, ever.** Your prompts, the peer messages injected into the\n thread, and the developer instructions the persona supplies are all withheld. The events channel\n carries a different read ACL from the channel you typed into, so republishing your own words there\n would widen who can read them. Assistant text, reasoning and tool activity are unaffected.\n- **Recovery after a broker outage.** Initial mesh absence still\n fails the host's readiness gate within 15 seconds, so it never opens an offline-looking TUI. Once\n ready, the plane publishes through the seat's reconnecting mesh endpoint: an outage can stop an\n emitter, and the first turn boundary after reconnect rebuilds it. A rebind DECLINES to publish two\n things, and they are one rule rather than two exceptions. It declines what the thread wrote while\n the seat was cut off. It also declines the\n turn whose own boundary triggered it: Codex writes a turn's first record before it announces that\n the turn started, and that announcement is what a rebind runs on, so the record is always behind\n whatever boundary the rebind takes, and a run is never opened from the middle of a turn. The first\n turn to start after the rebind is published in full. One case is different and is named here\n rather than left to be discovered: if the emitter had already been publishing this thread and\n then died, the seat's log carries its position, and the rebind CONTINUES that log rather than\n starting where it binds. The rebind publishes the complete outage backlog once the plane is back,\n including everything the thread wrote after the previous emitter became terminal. Two consequences\n are worth stating plainly, because both are easy to read past. A tool RESULT is published as the tool returned it, so anything a tool read on\n the seat's behalf, including messages it fetched from a channel with a narrower reader set, is in\n this stream; nothing redacts it or marks where it came from. And a backlog written while the\n plane was terminal is not discarded, it is delivered on recovery. Together those mean the readers\n of an events channel must be treated as at least as wide as every channel the seat's own tools\n can read. What the stream does not carry, here or on a live plane, is the session's own record of\n the user's words and the developer instructions. Neither of those two carriers is introduced by\n the boundary rule above and neither changes shape, but the rule is not confined to the seat whose\n emitter never started. It changes WHICH RECORDS reach the stream, on every armed seat. A bind\n announces where the stream starts and the emitter's setup then runs before its first read; what\n the thread appended inside that window used to land behind the cursor and be dropped, and it is\n published now. A whole turn can sit in there, tool results included, so the carrier described\n just above now covers a stretch of the session it previously lost. Nothing is sent twice in\n either case.\n\n And the reader set is a requirement rather than a guarantee, which is the last thing to say\n plainly. The grant does not enforce it, and it is worth being exact about what does. A spawn\n through the manager gives a seat publish rights on its own event channel and nothing else, and a\n spawn whose grant names a different agent's event channel is refused at the door. That fence is\n the manager's, it reads the concrete form and leaves a pattern such as `events.<owner>.>` to\n ordinary ACL authority, and a foreground `cotal spawn` on your own machine grants whatever you\n name because it mints from your own signing material. [connect-claude.md](connect-claude.md#event-plane)\n spells all three out. Who may READ a plane is minted separately and out of band either way, with\n `cotal actor grant` on a user-auth mesh and `cotal mint --profile agent --allow-subscribe` on a\n static one. So holding the events readers to at least the width of every channel the seat's tools\n can read is the operator's policy to keep, enforced by whoever mints those readers.\n- **Reasoning is published as its summary only.** Codex also stores an encrypted reasoning blob on\n every reasoning record; it is opaque, no reader can display it, and it is never put on the wire.\n\n## Sandbox autonomy\n\nA spawned Codex agent is woken by peer messages, which arrive when nobody is watching the\nterminal. The defaults follow from that, and all three are overridable per spawn with `--opt`.\n\n| Default | What it means |\n| --- | --- |\n| `approval_policy=\"never\"` | Never **ask** before running a command. Not \"refuse\": the agent runs its commands, it just does not stop to prompt. An interactive policy is refused loud rather than honored dishonestly, because a mesh-driven turn would block forever on a prompt nobody sees, and the alternative (auto-answering for you) nullifies the policy you asked for. |\n| `sandbox_mode=\"workspace-write\"` | Commands may read anywhere but write only inside the agent's workspace. This, not the prompt, is the part that is actually enforced; see below for the (real) exposure it leaves. |\n| `sandbox_workspace_write={network_access=true}` | Network **on** inside that sandbox. Codex's own default is off, which breaks installing a dependency, pushing a branch, or calling an API, with an error that reads like the task is impossible rather than the sandbox saying no. Applied only when the sandbox is actually `workspace-write`: tighten the mode and no network grant is emitted at all. |\n\nWhat the sandbox guarantees, stated literally: it **blocks out-of-workspace local filesystem\nwrites**. It does **not** block reads, exfiltration, or networked side effects.\n\nAll three of those are live with the defaults above, because a peer's message is a **remote input**\nthat can cause this agent to run commands. A confused or hostile peer can in principle get it to\nread a file elsewhere on your machine and send it; reach loopback or link-local services; or act\nthrough any credential it can read, which includes irreversible actions: a force-push, an API\ndelete, a deploy. Containing filesystem writes is therefore not the same as containing damage, and\nit should not be read that way. It is still worth keeping, because it is the one class this sandbox\ncan actually enforce.\n\nIf that exposure is wrong for a given agent, turn the network back off (below), tighten the mode,\nor run it under a separate OS user; the same point is repeated under [Limits](#limits) so it\nsurvives a skim. The spawn capability is the trust boundary for *who* may create an agent; the\nsandbox bounds one class of what it can then be talked into doing, not all of it.\n\nTune it per spawn:\n\n```bash\ncotal spawn --agent codex --opt sandbox_mode=read-only # tightest: no writes\ncotal spawn --agent codex --opt 'sandbox_workspace_write={network_access=false}' # contained, offline\ncotal spawn --agent codex --opt sandbox_mode=danger-full-access # no sandbox at all\n```\n\n`danger-full-access` is Codex's own name for it and means what it says: the agent may write\nanywhere your user account can. Codex documents that mode as intended only for environments that\nare already externally sandboxed (a container, a VM), not a workstation. On a laptop, prefer\ntightening the workspace over removing the sandbox.\n\n## Limits\n\n- **The sandbox blocks out-of-workspace filesystem writes, and only that.** It does not block\n reads, exfiltration, or networked side effects. With the default `workspace-write` + network on,\n a peer-driven turn can read anything your user account can (`~/.ssh`, `~/.aws`, `.env` files, the\n agent's own `auth.json`) and send it; reach loopback and link-local services; and act through any\n credential it can read, including irreversibly (a force-push, an API delete, a deploy). Only\n local writes outside the workspace are stopped, so this is not \"everything risky is reversible\"\n and not \"the only exposure is disclosure\". If that is wrong for a given agent, spawn it with\n `--opt 'sandbox_workspace_write={network_access=false}'` or `--opt sandbox_mode=read-only`, or\n run it as a separate OS user. See [Sandbox autonomy](#sandbox-autonomy).\n- **Not a boundary between agents on one machine.** The app-server listener and the tool\n endpoint are both loopback-bound and token-authenticated, which keeps out other OS users and\n anything off-box. It is not isolation between *managed agents*, which run as the same user and\n can therefore reach each other's tokens; a hostile agent on your workstation could drive\n another's Codex or speak as it on the mesh. Run mutually distrusted agents under separate OS\n users or separate machines.\n- **The TUI is local-only.** The app-server listener binds loopback and nothing else, so\n attaching Codex's UI to an agent on another machine needs your own SSH port-forward; there is\n no built-in remote attach. `cotal attach` (which streams the manager's pty) is the supported\n way to reach a detached agent.\n- **No session resume.** `cotal spawn --resume <id>` throws: a resumed codex thread comes up\n without its configured MCP servers, so the agent would be mute on the mesh.\n- **No tool-sharing.** `connectors.codex.mcpServers` is not implemented and throws if set.\n- **Experimental upstream surface.** `codex app-server` is labeled experimental by OpenAI (it\n is also what the Codex TUI itself runs on). The connector pins every protocol shape in one\n driver file and re-proves the contract with a gated live smoke (`COTAL_E2E_CODEX=1`).\n\n## See also\n\n- [Connectors](connectors.md): the feature matrix across all connectors\n- [Run a mesh](run-a-mesh.md) \xB7 [Define a team](define-a-team.md) \xB7 [Watch a mesh](watch-a-mesh.md)\n- [MCP tools](mcp-tools.md) \xB7 [Connect Claude Code](connect-claude.md) \xB7 [Connect OpenCode](connect-opencode.md)\n"
|
|
56166
56166
|
},
|
|
56167
56167
|
{
|
|
56168
56168
|
"slug": "connect-hermes",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/pi",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.9",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"esbuild": "^0.28.0",
|
|
35
35
|
"typebox": "^1.1.0",
|
|
36
36
|
"zod": "^4.4.3",
|
|
37
|
-
"@cotal-ai/
|
|
38
|
-
"@cotal-ai/core": "0.33.
|
|
37
|
+
"@cotal-ai/core": "0.33.9",
|
|
38
|
+
"@cotal-ai/connector-core": "0.33.9"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"dist"
|