https://github.com/d4j3y2k/oxtail
Peer awareness for parallel AI coding-agent sessions, over MCP
https://github.com/d4j3y2k/oxtail
agents claude-code codex mcp model-context-protocol tmux
Last synced: 1 day ago
JSON representation
Peer awareness for parallel AI coding-agent sessions, over MCP
- Host: GitHub
- URL: https://github.com/d4j3y2k/oxtail
- Owner: d4j3y2k
- License: mit
- Created: 2026-05-11T00:08:32.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-18T00:30:04.000Z (6 days ago)
- Last Synced: 2026-06-18T01:19:12.395Z (6 days ago)
- Topics: agents, claude-code, codex, mcp, model-context-protocol, tmux
- Language: TypeScript
- Size: 1.11 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# oxtail
[](https://github.com/d4j3y2k/oxtail/actions/workflows/test.yml)
[](https://www.npmjs.com/package/oxtail)
[](LICENSE)
[](package.json)
**Let your parallel AI coding agents see each other, message each other, and hand off
work — with no human relaying between them.**
oxtail is a local [MCP](https://modelcontextprotocol.io) server. Point two or more
agent sessions — Claude Code, Codex CLI, or a mix — at it in the same project, and they
gain peer awareness: each can list the others, see what they're working on, message
them, delegate tasks that survive across turns, and watch the whole fleet from a
cockpit. Everything stays **local to one machine and one project** — no network
listener, no cross-project visibility.
[Quick start](#quick-start) · [The cockpit](#the-fleet-cockpit) · [Concepts](#core-concepts) · [MCP tools](#mcp-tools) · [Configuration](#configuration) · [Protocol](docs/protocol.md) · [Security](SECURITY.md) · [Changelog](CHANGELOG.md)
```text
oxpit myproject 4 agents (3 active)
agent type status work / purpose
🟢 main* claude active 8s ⏳ ↔ oxtail awaiting codex: token-refresh audit
🟢 reviewer claude active 1m ✎ edit addressing review comments
🟢 codex codex active 30s ⚙ bash ⚑1 auditing the token refresh path
🟡 tests claude idle 8m ✉2
wait-graph
⏳ main awaiting reply from codex (2m)
comms recent message tail
2m main → codex ⚑ please audit the token refresh path and report findings
1m reviewer → main left 3 comments on the PR, see inline
20s codex → main on it — tracing the refresh path now
```
Illustrative output from `oxtail status` / `oxpit` — one engine, two entry points.
Run it with `npx oxtail oxpit` once your agents are working in the project.
## Why
If you run more than one coding agent at a time, they're usually blind to each other —
you become the message bus, copy-pasting context between terminals. oxtail removes you
from that loop:
- **Peer awareness, cheaply.** An agent learns what its peers are doing from a small
`state` card — no need to read a whole transcript to find out "who's touching the
auth module?"
- **Real messaging, not just discovery.** Agents send messages, ask blocking
questions (`ask_peer`), and reply by id — correlated, so an answer maps back to its
question.
- **Delegation that survives.** Hand off a task as a durable *obligation* the receiver
owns until it's done — it doesn't evaporate if a notification is missed.
- **Works across clients.** Claude Code and Codex CLI both speak MCP, so a Claude can
delegate to a Codex and vice-versa.
- **A live cockpit.** `oxpit` shows the whole fleet — who's active, who's waiting on
whom (with deadlock detection), and the inter-agent conversation as it happens.
- **Local and scoped by design.** stdio MCP server (no open port); visibility is
per-project; the trust boundary is your single local user.
## Quick start
**1. Register oxtail with your agent client.** It's fetched from npm on first use.
Claude Code — add to `~/.claude.json` (global) or a project's `.mcp.json`:
```jsonc
{ "mcpServers": { "oxtail": { "command": "npx", "args": ["-y", "oxtail@latest"] } } }
```
Codex CLI — add to `~/.codex/config.toml`:
```toml
[mcp_servers.oxtail]
command = "npx"
args = ["-y", "oxtail@latest"]
```
> Pin a version (`oxtail@0.25.0`) for daily configs; `@latest` is fine for trying it
> out. On Windows, wrap the command as `cmd /c npx -y oxtail@latest`.
**2. (Claude Code) Install the hooks** so agents receive messages autonomously and
auto-join the registry:
```sh
npx oxtail install-hook
```
This is what lets a Claude session get a peer's message *mid-turn* instead of only when
it next polls. Codex receives by reading its inbox at a turn boundary. ([Why the
asymmetry?](docs/protocol.md#mid-turn-vs-next-turn-delivery-the-asymmetry))
**3. Watch your fleet** from any separate terminal in the same repo:
```sh
npx oxtail oxpit # live interactive cockpit
npx oxtail status # print once and exit (scriptable, --json)
```
That's it. Start a second agent in the same project and they'll see each other. To let
agents message without a per-call approval prompt, see
[Configuration](#configuration).
**Requirements:** Node 20+, and `tmux` on `PATH` (for the cockpit and for waking idle
peers).
## The fleet cockpit
`oxtail oxpit` (or the standalone `oxpit` command after `npm i -g oxtail`) is a
read-only mission-control view of every agent in a project. `oxtail status` is the
same engine as a one-shot print.
- **Liveness & activity** — a glyph (🟢 active / 🟡 idle / ⚫ dead) with the raw age,
plus a live **tool badge** (`⚙ bash` `↔ oxtail` `✎ edit` `▤ read` …) read from a
transcript tail, and the selected agent's live pane-tail.
- **The wait-graph** — who is awaiting whom, flagging a `⛔ DEADLOCK` only when every
member of a wait cycle is alive, and an orphaned wait when a target has died. This is
the one thing you can't see by tabbing through panes.
- **Badges** — `✉N` unread · `⚑N` open obligations · `⏳` awaiting a peer reply.
- **The comms-log** (`l`) — the inter-agent conversation as a chronological feed, with
delegation (`⚑`/`⚑✓`/`⚑✗`) and ask/reply (`❓`/`↩`) markers.
Keys: `↑↓`/`jk` select · `⏎` jump to that agent's pane · `n` nudge · `m` message · `l`
comms-log · `w` open thread · `?` help · `⌃C` quit.
**Monitoring is read-only by default** — the cockpit never drains a mailbox or takes a
lock, and infers liveness, work, and waits from observed facts rather than
self-reported state. Its only writes are two *explicit, opt-in* actions: a
human-authored **operator message** (delivered through the same path agents use, framed
to the receiver as untrusted, one-way context), and **fleet lifecycle** commands —
stand up, converge, or reset whole tmux agent-fleets from a `.oxtail/fleet.json` spec,
every mutation dry-run by default and guarded so it can only ever touch panes it
created (see the [changelog](CHANGELOG.md) for the SPAWN / SYNC / RESET model).
## Core concepts
**Project-scoped, never global.** Sessions in `/path/to/foo` see each other; sessions
elsewhere don't. Cross-project sends and reads are rejected, by design.
**Identity is the session, not the process.** An agent is its `client.session_id`, not
its pid or tmux name. One client can be backed by several MCP server children;
mailboxes are keyed by session identity so a process restart can never strand mail.
**State cards over transcripts.** `set_my_state({ purpose })` is the cheap way to tell
peers what you're doing. `read_session` exists for the deep dive — but it's
**browse/diagnostic only, never proof a peer replied** (the transcript can lag a
rotated thread; confirm replies via the mailbox).
**Messaging is durable and correlated.** Every delivered message is recorded in a
per-session received-ledger *before* it's visible, so a reply handle always resolves.
`ask_peer` blocks for an answer and is **durable on timeout** — let it time out, end
your turn, and the late reply wakes you back, even hours later.
**Delegation is an obligation, not a notification.** `send_message({ action_required:
true })` gives the receiver an OPEN obligation it discovers via `my_open_work` and
closes with `complete_work` / `block_work`. Correctness lives on disk, off the wake
path — so a missed notification never loses the work. **Waking is an accelerator, not
the source of truth.**
**Waking is conservative.** A plain message doesn't wake an idle peer; `wake: "auto"`
does, but it's state-gated (it won't type into a peer that's mid-turn) and only ever
targets the pane the live process tree confirms hosts that peer. Full model:
[docs/protocol.md](docs/protocol.md#waking-an-idle-peer).
## MCP tools
A compact summary; full per-tool semantics and caveats are in
[docs/tools.md](docs/tools.md).
| Tool | Purpose | Key caveat / signal |
|---|---|---|
| **— Discovery & state —** | | |
| `list_project_sessions` | List peers in a project root, with `client_type` + `state` card | One row per agent; dedupe shared names via `client_session_id` |
| `set_my_state` | Write a `purpose` card (≤200 chars) peers can read cheaply | — |
| `get_my_session` | This server's registry entry + identity-detection diagnosis | Carries `next_step` when identity is unresolved |
| `claim_session` | Register this session's id (the routine join path) | Monotonic — survives later auto-detection |
| `register_my_session` | Pin the id directly | Debug escape hatch; prefer `claim_session` |
| **— Read & diagnose —** | | |
| `read_session` | A peer's recent transcript (clean turns, or raw pane) | **Diagnostic only, not proof of a reply**; carries freshness/provenance |
| `message_status` | Did my message land? | `delivered` / `pending` / `unknown`; delivery-into-context, not "acted on" |
| **— Messaging —** | | |
| `send_message` | Fire-and-forget to a peer (≤8KB) | Doesn't wake unless `wake:"auto"`; `action_required:true` → delegation |
| `read_my_messages` | Drain this session's inbox | Surfaces `open_work_count`; hooks may have already drained it |
| `reply_to_message` | Reply by `message_id` (derives target + correlation) | Fail-closed on unknown/aged-out id; you can only reply to *your* mail |
| `ask_peer` | Delegate-and-wait: block for a correlated reply | Durable on timeout — late reply wakes you back |
| **— Durable delegation —** | | |
| `my_open_work` | Delegations you own but haven't closed | The pull source of truth; rediscover work after any missed wake |
| `complete_work` | Close an obligation DONE + notify the requester | Atomic; reverts to OPEN if the result can't be delivered |
| `block_work` | Close an obligation BLOCKED + tell the requester why | Keeps a stuck task out of your open set |
### Usage sketch
```js
// Join
claim_session({ session_id: "<$CLAUDE_CODE_SESSION_ID or $CODEX_THREAD_ID>" })
set_my_state({ purpose: "wiring up the mailbox" })
// Discover & read
list_project_sessions({ project_root: "/path/to/project" })
read_session({ name: "reviewer" }) // browse only — not proof of a reply
// Message & reply
send_message({ target: "reviewer", body: "checking in" })
read_my_messages()
reply_to_message({ message_id: "", body: "..." })
// Delegate-and-wait, and durable delegation
ask_peer({ target: "codex", body: "[Handoff] audit the token refresh path; report back" })
send_message({ target: "codex", body: "[Task] migrate the config loader", action_required: true })
// receiver: my_open_work() → do it → complete_work({ message_id, body: "done: ..." })
```
## Configuration
**Permissions (recommended for autonomous collaboration).** So agents can initiate
delegation without a per-call approval prompt, add to `~/.claude.json`:
```jsonc
{ "permissions": { "allow": [
"mcp__oxtail__ask_peer",
"mcp__oxtail__send_message",
"mcp__oxtail__read_my_messages"
] } }
```
(Without an allowlist, Claude Code prompts on first use with an "always allow" option —
pick that once per project for the same effect.)
**Hooks.** `npx oxtail install-hook` manages three Claude Code events (`PreToolUse`,
`Stop`, `UserPromptSubmit`), preserving existing third-party entries. **Re-run it after
upgrading** when the hook version bumps (the server warns if you don't).
`npx oxtail uninstall-hook` reverses it.
Environment variables
| Variable | Default | Effect |
|---|---|---|
| `OXTAIL_ASK_PEER_TIMEOUT_MS` | `60000` | `ask_peer` blocking timeout (lower if your client aborts tool calls sooner) |
| `OXTAIL_ASK_PEER_MAX_TIMEOUT_MS` | `100000` | Hard ceiling a per-call `timeout_ms` is clamped to (keeps a wait under the client's abort window) |
| `OXTAIL_ASK_PEER_WAKE_STRATEGY` | `auto` | `auto` \| `legacy` \| `off` per-client wake routing / rollback |
| `OXTAIL_AUTOWAKE` | `on` | `off` disables reply auto-wake entirely |
| `OXTAIL_AUTOWAKE_FRESH_IDLE_MS` | `300000` | How recently-idle a requester must be for a reply to auto-wake it |
| `OXTAIL_WAKE_DEBOUNCE_MS` | `1000` | Coalesce rapid repeat wakes to one peer |
| `OXTAIL_PENDING_ASK_TTL_MS` | `3600000` | How long a timed-out `ask_peer` waits for a late reply that wakes you back |
| `OXTAIL_ACTIVITY_BUSY_TTL_MS` | `600000` | When a quiet active turn ages to stale-busy (and becomes wakeable) |
| `OXTAIL_HOOK_MAX_BODY_CHARS` | `24000` | Budget for hook-injected message bodies |
| `OXTAIL_RECEIVED_MAX` | `1000` | Received-ledger retention (open obligations are exempt from pruning) |
| `MCP_TRACE_FILE` | unset | NDJSON trace of identity detection + wake outcomes (`oxtail diagnose` summarizes) |
Commonly tuned, not exhaustive — the autowake rate-limit/dedupe knobs and other
internals are covered in [docs/protocol.md](docs/protocol.md).
## How it works
Claude Code doesn't pass its session id to MCP children, so oxtail resolves identity
with a layered strategy: `env` → `hook-drop` (the SessionStart auto-join) → `birth-time`
fingerprint → the `claim_session` escape hatch. Once an id is set it's monotonic; only
an explicit claim can change it. Each server writes a small record to
`~/.oxtail/sessions/.json` that siblings read; records auto-clean on exit and on
read. The full resolution, mailbox keying, wake routing, and crash-consistency design
are in [docs/protocol.md](docs/protocol.md).
## Security & privacy
oxtail is for **one user, on one machine**, coordinating their own agents — the trust
boundary is your local Unix user, like `~/.ssh/`.
- **No network listener.** stdio MCP server: no open port, no HTTP server. (Installing
from npm is a separate, install-time event.)
- **Local & private.** State lives under `~/.oxtail/` (mode `0o700`/`0o600`); with
those permissions other Unix users can't read it. Nothing leaves the machine.
- **Messages are context, not authority.** Peer and operator messages are delivered as
context to weigh, never as privileged instructions; provenance is not authentication.
- **Don't run on shared-tenancy hosts.** Any process under your user can inject context
into an agent — that's also what makes the tool work.
The full threat model, supply-chain posture, and operator-message provenance are in
[SECURITY.md](SECURITY.md).
## Contributing
```sh
git clone https://github.com/d4j3y2k/oxtail && cd oxtail && npm install && npm test
```
oxtail is built by [dogfooding](AGENTS.md) — features land only after real
parallel-agent work surfaces the friction that names them. Design principles, scope,
and invariants worth defending are in [AGENTS.md](AGENTS.md). Release history is in
[CHANGELOG.md](CHANGELOG.md).
## License
[MIT](LICENSE) © David Kim