{"id":51054449,"url":"https://github.com/d4j3y2k/oxtail","last_synced_at":"2026-06-22T20:00:39.197Z","repository":{"id":357653527,"uuid":"1235029032","full_name":"d4j3y2k/oxtail","owner":"d4j3y2k","description":"Peer awareness for parallel AI coding-agent sessions, over MCP","archived":false,"fork":false,"pushed_at":"2026-06-18T00:30:04.000Z","size":1169,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T01:19:12.395Z","etag":null,"topics":["agents","claude-code","codex","mcp","model-context-protocol","tmux"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d4j3y2k.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-11T00:08:32.000Z","updated_at":"2026-06-17T23:41:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/d4j3y2k/oxtail","commit_stats":null,"previous_names":["d4j3y2k/oxtail"],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/d4j3y2k/oxtail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d4j3y2k%2Foxtail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d4j3y2k%2Foxtail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d4j3y2k%2Foxtail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d4j3y2k%2Foxtail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d4j3y2k","download_url":"https://codeload.github.com/d4j3y2k/oxtail/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d4j3y2k%2Foxtail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34622272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","claude-code","codex","mcp","model-context-protocol","tmux"],"created_at":"2026-06-22T20:00:33.247Z","updated_at":"2026-06-22T20:00:39.192Z","avatar_url":"https://github.com/d4j3y2k.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# oxtail\n\n[![test](https://github.com/d4j3y2k/oxtail/actions/workflows/test.yml/badge.svg)](https://github.com/d4j3y2k/oxtail/actions/workflows/test.yml)\n[![npm](https://img.shields.io/npm/v/oxtail.svg)](https://www.npmjs.com/package/oxtail)\n[![license](https://img.shields.io/npm/l/oxtail.svg)](LICENSE)\n[![node](https://img.shields.io/node/v/oxtail.svg)](package.json)\n\n**Let your parallel AI coding agents see each other, message each other, and hand off\nwork — with no human relaying between them.**\n\noxtail is a local [MCP](https://modelcontextprotocol.io) server. Point two or more\nagent sessions — Claude Code, Codex CLI, or a mix — at it in the same project, and they\ngain peer awareness: each can list the others, see what they're working on, message\nthem, delegate tasks that survive across turns, and watch the whole fleet from a\ncockpit. Everything stays **local to one machine and one project** — no network\nlistener, no cross-project visibility.\n\n[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)\n\n```text\noxpit  myproject  4 agents (3 active)\n     agent       type    status        work / purpose\n  🟢 main*       claude  active 8s  ⏳   ↔ oxtail   awaiting codex: token-refresh audit\n  🟢 reviewer    claude  active 1m       ✎ edit     addressing review comments\n  🟢 codex       codex   active 30s      ⚙ bash ⚑1  auditing the token refresh path\n  🟡 tests       claude  idle 8m         ✉2\n\nwait-graph\n  ⏳ main awaiting reply from codex (2m)\n\ncomms  recent message tail\n  2m   main → codex ⚑   please audit the token refresh path and report findings\n  1m   reviewer → main  left 3 comments on the PR, see inline\n  20s  codex → main     on it — tracing the refresh path now\n```\n\n\u003csup\u003eIllustrative output from `oxtail status` / `oxpit` — one engine, two entry points.\nRun it with `npx oxtail oxpit` once your agents are working in the project.\u003c/sup\u003e\n\n## Why\n\nIf you run more than one coding agent at a time, they're usually blind to each other —\nyou become the message bus, copy-pasting context between terminals. oxtail removes you\nfrom that loop:\n\n- **Peer awareness, cheaply.** An agent learns what its peers are doing from a small\n  `state` card — no need to read a whole transcript to find out \"who's touching the\n  auth module?\"\n- **Real messaging, not just discovery.** Agents send messages, ask blocking\n  questions (`ask_peer`), and reply by id — correlated, so an answer maps back to its\n  question.\n- **Delegation that survives.** Hand off a task as a durable *obligation* the receiver\n  owns until it's done — it doesn't evaporate if a notification is missed.\n- **Works across clients.** Claude Code and Codex CLI both speak MCP, so a Claude can\n  delegate to a Codex and vice-versa.\n- **A live cockpit.** `oxpit` shows the whole fleet — who's active, who's waiting on\n  whom (with deadlock detection), and the inter-agent conversation as it happens.\n- **Local and scoped by design.** stdio MCP server (no open port); visibility is\n  per-project; the trust boundary is your single local user.\n\n## Quick start\n\n**1. Register oxtail with your agent client.** It's fetched from npm on first use.\n\nClaude Code — add to `~/.claude.json` (global) or a project's `.mcp.json`:\n\n```jsonc\n{ \"mcpServers\": { \"oxtail\": { \"command\": \"npx\", \"args\": [\"-y\", \"oxtail@latest\"] } } }\n```\n\nCodex CLI — add to `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.oxtail]\ncommand = \"npx\"\nargs = [\"-y\", \"oxtail@latest\"]\n```\n\n\u003e Pin a version (`oxtail@0.25.0`) for daily configs; `@latest` is fine for trying it\n\u003e out. On Windows, wrap the command as `cmd /c npx -y oxtail@latest`.\n\n**2. (Claude Code) Install the hooks** so agents receive messages autonomously and\nauto-join the registry:\n\n```sh\nnpx oxtail install-hook\n```\n\nThis is what lets a Claude session get a peer's message *mid-turn* instead of only when\nit next polls. Codex receives by reading its inbox at a turn boundary. ([Why the\nasymmetry?](docs/protocol.md#mid-turn-vs-next-turn-delivery-the-asymmetry))\n\n**3. Watch your fleet** from any separate terminal in the same repo:\n\n```sh\nnpx oxtail oxpit       # live interactive cockpit\nnpx oxtail status      # print once and exit (scriptable, --json)\n```\n\nThat's it. Start a second agent in the same project and they'll see each other. To let\nagents message without a per-call approval prompt, see\n[Configuration](#configuration).\n\n**Requirements:** Node 20+, and `tmux` on `PATH` (for the cockpit and for waking idle\npeers).\n\n## The fleet cockpit\n\n`oxtail oxpit` (or the standalone `oxpit` command after `npm i -g oxtail`) is a\nread-only mission-control view of every agent in a project. `oxtail status` is the\nsame engine as a one-shot print.\n\n- **Liveness \u0026 activity** — a glyph (🟢 active / 🟡 idle / ⚫ dead) with the raw age,\n  plus a live **tool badge** (`⚙ bash` `↔ oxtail` `✎ edit` `▤ read` …) read from a\n  transcript tail, and the selected agent's live pane-tail.\n- **The wait-graph** — who is awaiting whom, flagging a `⛔ DEADLOCK` only when every\n  member of a wait cycle is alive, and an orphaned wait when a target has died. This is\n  the one thing you can't see by tabbing through panes.\n- **Badges** — `✉N` unread · `⚑N` open obligations · `⏳` awaiting a peer reply.\n- **The comms-log** (`l`) — the inter-agent conversation as a chronological feed, with\n  delegation (`⚑`/`⚑✓`/`⚑✗`) and ask/reply (`❓`/`↩`) markers.\n\nKeys: `↑↓`/`jk` select · `⏎` jump to that agent's pane · `n` nudge · `m` message · `l`\ncomms-log · `w` open thread · `?` help · `⌃C` quit.\n\n**Monitoring is read-only by default** — the cockpit never drains a mailbox or takes a\nlock, and infers liveness, work, and waits from observed facts rather than\nself-reported state. Its only writes are two *explicit, opt-in* actions: a\nhuman-authored **operator message** (delivered through the same path agents use, framed\nto the receiver as untrusted, one-way context), and **fleet lifecycle** commands —\nstand up, converge, or reset whole tmux agent-fleets from a `.oxtail/fleet.json` spec,\nevery mutation dry-run by default and guarded so it can only ever touch panes it\ncreated (see the [changelog](CHANGELOG.md) for the SPAWN / SYNC / RESET model).\n\n## Core concepts\n\n**Project-scoped, never global.** Sessions in `/path/to/foo` see each other; sessions\nelsewhere don't. Cross-project sends and reads are rejected, by design.\n\n**Identity is the session, not the process.** An agent is its `client.session_id`, not\nits pid or tmux name. One client can be backed by several MCP server children;\nmailboxes are keyed by session identity so a process restart can never strand mail.\n\n**State cards over transcripts.** `set_my_state({ purpose })` is the cheap way to tell\npeers what you're doing. `read_session` exists for the deep dive — but it's\n**browse/diagnostic only, never proof a peer replied** (the transcript can lag a\nrotated thread; confirm replies via the mailbox).\n\n**Messaging is durable and correlated.** Every delivered message is recorded in a\nper-session received-ledger *before* it's visible, so a reply handle always resolves.\n`ask_peer` blocks for an answer and is **durable on timeout** — let it time out, end\nyour turn, and the late reply wakes you back, even hours later.\n\n**Delegation is an obligation, not a notification.** `send_message({ action_required:\ntrue })` gives the receiver an OPEN obligation it discovers via `my_open_work` and\ncloses with `complete_work` / `block_work`. Correctness lives on disk, off the wake\npath — so a missed notification never loses the work. **Waking is an accelerator, not\nthe source of truth.**\n\n**Waking is conservative.** A plain message doesn't wake an idle peer; `wake: \"auto\"`\ndoes, but it's state-gated (it won't type into a peer that's mid-turn) and only ever\ntargets the pane the live process tree confirms hosts that peer. Full model:\n[docs/protocol.md](docs/protocol.md#waking-an-idle-peer).\n\n## MCP tools\n\nA compact summary; full per-tool semantics and caveats are in\n[docs/tools.md](docs/tools.md).\n\n| Tool | Purpose | Key caveat / signal |\n|---|---|---|\n| **— Discovery \u0026 state —** | | |\n| `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` |\n| `set_my_state` | Write a `purpose` card (≤200 chars) peers can read cheaply | — |\n| `get_my_session` | This server's registry entry + identity-detection diagnosis | Carries `next_step` when identity is unresolved |\n| `claim_session` | Register this session's id (the routine join path) | Monotonic — survives later auto-detection |\n| `register_my_session` | Pin the id directly | Debug escape hatch; prefer `claim_session` |\n| **— Read \u0026 diagnose —** | | |\n| `read_session` | A peer's recent transcript (clean turns, or raw pane) | **Diagnostic only, not proof of a reply**; carries freshness/provenance |\n| `message_status` | Did my message land? | `delivered` / `pending` / `unknown`; delivery-into-context, not \"acted on\" |\n| **— Messaging —** | | |\n| `send_message` | Fire-and-forget to a peer (≤8KB) | Doesn't wake unless `wake:\"auto\"`; `action_required:true` → delegation |\n| `read_my_messages` | Drain this session's inbox | Surfaces `open_work_count`; hooks may have already drained it |\n| `reply_to_message` | Reply by `message_id` (derives target + correlation) | Fail-closed on unknown/aged-out id; you can only reply to *your* mail |\n| `ask_peer` | Delegate-and-wait: block for a correlated reply | Durable on timeout — late reply wakes you back |\n| **— Durable delegation —** | | |\n| `my_open_work` | Delegations you own but haven't closed | The pull source of truth; rediscover work after any missed wake |\n| `complete_work` | Close an obligation DONE + notify the requester | Atomic; reverts to OPEN if the result can't be delivered |\n| `block_work` | Close an obligation BLOCKED + tell the requester why | Keeps a stuck task out of your open set |\n\n### Usage sketch\n\n```js\n// Join\nclaim_session({ session_id: \"\u003c$CLAUDE_CODE_SESSION_ID or $CODEX_THREAD_ID\u003e\" })\nset_my_state({ purpose: \"wiring up the mailbox\" })\n\n// Discover \u0026 read\nlist_project_sessions({ project_root: \"/path/to/project\" })\nread_session({ name: \"reviewer\" })            // browse only — not proof of a reply\n\n// Message \u0026 reply\nsend_message({ target: \"reviewer\", body: \"\u003csystem-reminder\u003echecking in\u003c/system-reminder\u003e\" })\nread_my_messages()\nreply_to_message({ message_id: \"\u003cid from hook / read_my_messages\u003e\", body: \"...\" })\n\n// Delegate-and-wait, and durable delegation\nask_peer({ target: \"codex\", body: \"[Handoff] audit the token refresh path; report back\" })\nsend_message({ target: \"codex\", body: \"[Task] migrate the config loader\", action_required: true })\n// receiver: my_open_work() → do it → complete_work({ message_id, body: \"done: ...\" })\n```\n\n## Configuration\n\n**Permissions (recommended for autonomous collaboration).** So agents can initiate\ndelegation without a per-call approval prompt, add to `~/.claude.json`:\n\n```jsonc\n{ \"permissions\": { \"allow\": [\n  \"mcp__oxtail__ask_peer\",\n  \"mcp__oxtail__send_message\",\n  \"mcp__oxtail__read_my_messages\"\n] } }\n```\n\n(Without an allowlist, Claude Code prompts on first use with an \"always allow\" option —\npick that once per project for the same effect.)\n\n**Hooks.** `npx oxtail install-hook` manages three Claude Code events (`PreToolUse`,\n`Stop`, `UserPromptSubmit`), preserving existing third-party entries. **Re-run it after\nupgrading** when the hook version bumps (the server warns if you don't).\n`npx oxtail uninstall-hook` reverses it.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eEnvironment variables\u003c/strong\u003e\u003c/summary\u003e\n\n| Variable | Default | Effect |\n|---|---|---|\n| `OXTAIL_ASK_PEER_TIMEOUT_MS` | `60000` | `ask_peer` blocking timeout (lower if your client aborts tool calls sooner) |\n| `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) |\n| `OXTAIL_ASK_PEER_WAKE_STRATEGY` | `auto` | `auto` \\| `legacy` \\| `off` per-client wake routing / rollback |\n| `OXTAIL_AUTOWAKE` | `on` | `off` disables reply auto-wake entirely |\n| `OXTAIL_AUTOWAKE_FRESH_IDLE_MS` | `300000` | How recently-idle a requester must be for a reply to auto-wake it |\n| `OXTAIL_WAKE_DEBOUNCE_MS` | `1000` | Coalesce rapid repeat wakes to one peer |\n| `OXTAIL_PENDING_ASK_TTL_MS` | `3600000` | How long a timed-out `ask_peer` waits for a late reply that wakes you back |\n| `OXTAIL_ACTIVITY_BUSY_TTL_MS` | `600000` | When a quiet active turn ages to stale-busy (and becomes wakeable) |\n| `OXTAIL_HOOK_MAX_BODY_CHARS` | `24000` | Budget for hook-injected message bodies |\n| `OXTAIL_RECEIVED_MAX` | `1000` | Received-ledger retention (open obligations are exempt from pruning) |\n| `MCP_TRACE_FILE` | unset | NDJSON trace of identity detection + wake outcomes (`oxtail diagnose` summarizes) |\n\nCommonly tuned, not exhaustive — the autowake rate-limit/dedupe knobs and other\ninternals are covered in [docs/protocol.md](docs/protocol.md).\n\n\u003c/details\u003e\n\n## How it works\n\nClaude Code doesn't pass its session id to MCP children, so oxtail resolves identity\nwith a layered strategy: `env` → `hook-drop` (the SessionStart auto-join) → `birth-time`\nfingerprint → the `claim_session` escape hatch. Once an id is set it's monotonic; only\nan explicit claim can change it. Each server writes a small record to\n`~/.oxtail/sessions/\u003cpid\u003e.json` that siblings read; records auto-clean on exit and on\nread. The full resolution, mailbox keying, wake routing, and crash-consistency design\nare in [docs/protocol.md](docs/protocol.md).\n\n## Security \u0026 privacy\n\noxtail is for **one user, on one machine**, coordinating their own agents — the trust\nboundary is your local Unix user, like `~/.ssh/`.\n\n- **No network listener.** stdio MCP server: no open port, no HTTP server. (Installing\n  from npm is a separate, install-time event.)\n- **Local \u0026 private.** State lives under `~/.oxtail/` (mode `0o700`/`0o600`); with\n  those permissions other Unix users can't read it. Nothing leaves the machine.\n- **Messages are context, not authority.** Peer and operator messages are delivered as\n  context to weigh, never as privileged instructions; provenance is not authentication.\n- **Don't run on shared-tenancy hosts.** Any process under your user can inject context\n  into an agent — that's also what makes the tool work.\n\nThe full threat model, supply-chain posture, and operator-message provenance are in\n[SECURITY.md](SECURITY.md).\n\n## Contributing\n\n```sh\ngit clone https://github.com/d4j3y2k/oxtail \u0026\u0026 cd oxtail \u0026\u0026 npm install \u0026\u0026 npm test\n```\n\noxtail is built by [dogfooding](AGENTS.md) — features land only after real\nparallel-agent work surfaces the friction that names them. Design principles, scope,\nand invariants worth defending are in [AGENTS.md](AGENTS.md). Release history is in\n[CHANGELOG.md](CHANGELOG.md).\n\n## License\n\n[MIT](LICENSE) © David Kim\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd4j3y2k%2Foxtail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd4j3y2k%2Foxtail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd4j3y2k%2Foxtail/lists"}