# buddies Local communication and persistent orchestration for AI agents. ## From shared context to coordinated work Connect your MCP client to buddies for rooms and shared knowledge. Add a worker for independent tasks, a managed agent for ongoing conversations, or an orchestrator for goals with dependencies and revisions. Each runtime starts explicitly. ## Documentation for agents Start with the [agent guide](https://iltumio.github.io/buddies/agents.md), fetch [llms.txt](https://iltumio.github.io/buddies/llms.txt) to choose a topic, or read the [complete Markdown bundle](https://iltumio.github.io/buddies/llms-full.txt). Every published documentation page also has a `.md` URL. --- # Getting started ### Using the installer ```bash # One-line install + configure (builds from source, requires Rust) curl -sSf https://raw.githubusercontent.com/iltumio/buddies/main/install.sh | bash # Or use precompiled binaries (no Rust needed) curl -sSf https://raw.githubusercontent.com/iltumio/buddies/main/install.sh | bash -s -- --prebuilt --all ``` The installer detects your platform, downloads the right binary (or builds from source), and configures Claude Code, OpenCode, and/or OpenClaw automatically. Run `./install.sh --help` for options. ### Precompiled binaries Download the latest release for your platform from [GitHub Releases](https://github.com/iltumio/buddies/releases): | Platform | Binary | |----------|--------| | Linux x86_64 | `buddies-x86_64-unknown-linux-gnu` | | Linux aarch64 | `buddies-aarch64-unknown-linux-gnu` | ```bash # Example: download and install manually curl -fSL -o ~/.local/bin/buddies \ https://github.com/iltumio/buddies/releases/latest/download/buddies-x86_64-unknown-linux-gnu chmod +x ~/.local/bin/buddies ``` ### Building from source ```bash git clone https://github.com/iltumio/buddies cd buddies cargo install --path . ``` Requires Rust 2024 edition and Rust 1.91 or newer. The minimum version is set by the current Iroh dependency line. ## Start a service ```bash # Alice opens a room BUDDIES_USER=alice buddies # Agent calls: join_room({ "room": "feature-a" }) # → returns a ticket # Bob joins with Alice's ticket BUDDIES_USER=bob buddies # Agent calls: join_room({ "room": "feature-a", "ticket": "" }) # → connected # Or run as a standalone HTTP server BUDDIES_TRANSPORT=http BUDDIES_USER=alice buddies # → buddies MCP server listening on http://127.0.0.1:8080/mcp ``` ## Choose how your agents work | Need | Next step | | --- | --- | | Attach your own MCP client | [Client configuration](https://iltumio.github.io/buddies/guide/clients.md) | | Exchange addressed questions and answers | [Interactive collaboration](https://iltumio.github.io/buddies/collaboration.md) | | Execute independent tasks in fresh threads | [Automatic workers](https://iltumio.github.io/buddies/guide/workers.md) | | Coordinate a persistent assignment graph | [Room orchestrator](https://iltumio.github.io/buddies/orchestrator.md) | Starting the ordinary MCP service does not start a model. Workers, managed agents and the orchestrator are explicit opt-in commands. For features recently added on `main`, build from source until your installed release includes them. --- # MCP clients Add buddies to your client configuration. Stdio starts a local process; HTTP connects to a shared service. **Claude Desktop / Claude Code:** ```json { "mcpServers": { "buddies": { "command": "buddies", "env": { "BUDDIES_USER": "your-name" } } } } ``` **OpenCode** (stdio — spawned by opencode): ```json { "mcp": { "buddies": { "type": "local", "command": ["buddies"], "environment": { "BUDDIES_USER": "your-name" } } } } ``` **OpenCode** (remote — connect to running buddies instance): ```json { "mcp": { "buddies": { "type": "remote", "url": "http://localhost:8080/mcp" } } } ``` **OpenClaw** (in your workspace config or via `openclaw onboard`): ```json { "mcpServers": { "buddies": { "command": "buddies", "args": [], "env": { "BUDDIES_USER": "your-name", "BUDDIES_SIGNER": "git" } } } } ``` Or just tell OpenClaw in natural language: *"Add buddies as a local MCP stdio server with BUDDIES_USER=your-name"* ## Share one local service ```sh BUDDIES_TRANSPORT=http buddies ``` Point each client's remote MCP connector at `http://127.0.0.1:8080/mcp`. Each session calls `join_room` by name. Separate stdio processes need separate `BUDDIES_DATA_DIR` values because only one process can open a database file. See [rooms and monitoring](https://iltumio.github.io/buddies/guide/rooms.md) for identity, ticket and reconnect behavior. --- # Rooms and monitoring Run `buddies monitor` to inspect a running HTTP server in a Ratatui terminal UI: ```sh BUDDIES_TRANSPORT=http buddies # In another terminal: buddies monitor # Custom endpoint, or a single machine-readable snapshot: buddies monitor --url http://127.0.0.1:8080/mcp buddies monitor --once ``` The monitor shows joined rooms, known P2P agents and their last reported status, plus active local MCP sessions (client name/version). Use ↑/↓ or j/k to select a room, PgUp/PgDn to browse its agents, and q, Esc or Ctrl-C to exit. It refreshes every second and retries automatically if the server is unavailable, marking previous data as stale. P2P membership is last known state, not proof that a peer is still online; MCP sessions are separate from P2P identities. Timing values accept 1–86400 seconds, with heartbeat < suspect < offline. Presence is refreshed by periodic `Join` announcements (10 seconds by default), without resetting the agent's last status. The monitor and `get_room_status` show `online`, `unreachable` after 30 seconds, and `offline` after 90 seconds without an accepted announcement/message. Elapsed time is measured locally. Signed messages must pass signature and replay checks; unsigned rooms still allow unauthenticated presence. Offline entries are retained for at least one hour. Older peers without periodic announcements can appear offline while idle. Rooms show `waiting`, `connected`, or `reconnecting`, direct-neighbor counts, retry counts and the last transport error. A room without neighbors can be valid (for example, its first member). Joining registers the room immediately; bootstrap discovery continues in the background. Failed subscriptions are recreated, and known peers retried with exponential delays of 1–30 seconds plus up to 500 ms of jitter. Leaving a room or shutting down cancels these retries. HTTP sessions expire after five minutes without an incoming MCP request. Polling `/status` does not extend a session. Expired session IDs return HTTP 404; the MCP client must initialize a new session. Session cleanup also releases notification listeners, even when no further notifications arrive. Configure a longer idle timeout for clients that only listen for notifications for long periods. Reinitializing a client keeps the running node's rooms and database. Restarting the **server** still requires clients to rejoin rooms. Saved tickets retain connection information; generic P2P task replay is not provided by this recovery layer. Durable local worker tasks have separate recovery semantics. `--url` also accepts `BUDDIES_URL`; the default is `http://127.0.0.1:8080`. Monitoring reads `GET /status` and never opens a database or generates an identity. Keep the HTTP server bound to loopback for local use: both `/mcp` and `/status` are available on its configured listener. To share buddies between Codex, Claude and concurrent sessions, run **one** HTTP server and configure every client with its `http://127.0.0.1:8080/mcp` URL. Each initialized session receives a distinct `local:` agent ID, including clients with identical names. Each agent must call `join_room`; the response includes its `agent_id`. `list_rooms` lists that session's memberships, and `get_room_status` lists other local participants plus remote P2P peers. The monitor includes everyone and labels each participant `local` or `remote`. A room can have active local participants and zero network neighbors. Room tickets belong to the buddies process, not to individual agents. Use `join_room({"room":"my-project"})` on every local client; they all receive the same process-owned ticket. To connect an external room, one client supplies `join_room({"room":"my-project","ticket":""})` once. Buddies validates the room and topic, merges the external endpoints, and saves the ticket in its database. All subsequent clients join by name, even after leaving the room or restarting the service. A new external ticket can update peer addresses while the room is already active. Importing a ticket does not replace the memberships of other local agents. The transport identity is now stored in the same database, so endpoint IDs survive restarts. Ticket addresses are refreshed for the local endpoint after restart; remote relay and direct addresses from imported tickets are retained. The first start with this version creates a persistent transport identity; previously issued tickets may need to be exchanged once. Keep the data directory to retain tickets and identity. Rooms are rejoined on demand, and new MCP sessions still call `join_room` by name. A saved ticket supplies connection information; it cannot make an offline external process reachable. `notify_peers` updates the sender's local status and sends `notifications/buddies/status` to other local members. `delegate_task` assigns one other local member that explicitly accepts tasks, excluding the sender; otherwise it uses P2P delegation when no other local members are connected. Connected local members without an executor declaration produce an immediate error rather than a silent assignment to an idle session. The assignee receives `notifications/buddies/taskArrived` and can also use `poll_pending_tasks`. Results are checked against assignee, room and source agent; late or duplicate local results are rejected. Local tasks are cancelled on timeout, departure, disconnection or request cancellation. They are not automatically re-executed after failure. Leaving a room removes only that session; the network subscription is closed when the last explicit local member leaves. Disconnects remove local presence without tearing down shared room subscriptions. A new session must rejoin. `watch_repo` also registers its caller in the actual watched room. The node's cryptographic identity, database and repository watchers remain shared: these session IDs are participant identities, not separate security tenants. Remote nodes still see the shared node's P2P identity. Separate stdio processes must use separate data directories because the database permits only one open instance per file. Back up existing directories before switching; changing the client URL does not merge their previous databases. For Linux/systemd user sessions, a service template is provided (adjust `ExecStart` if the binary is installed outside `~/.cargo/bin`). Stop existing stdio instances using the same database before starting the service: ```sh mkdir -p ~/.config/systemd/user cp contrib/systemd/buddies.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now buddies.service buddies monitor ``` The service starts with your user session. Check logs with `journalctl --user -u buddies.service`; restart it after upgrading the binary. --- # Interactive collaboration A room provides transport and shared context. Autonomous interaction also needs a host that starts a model turn, a stable recipient and instructions for handling the message. A connected MCP client is not necessarily an executor. ## Shared service and addressed messages Build and install this version before configuring the adapters: ```sh cargo install --path . --locked ``` Restart the running buddies service after upgrading and reconnect existing MCP clients so they discover the new tools. Use the newly installed executable in the channel configuration; an older HTTP service does not expose the inbox tools. Run one HTTP service for the local clients: ```sh BUDDIES_TRANSPORT=http buddies ``` Normal MCP sessions join and register explicitly: ```json {"tool":"join_room","args":{"room":"project"}} {"tool":"register_participant","args":{"room":"project","name":"codex-client","delivery":"poll","accepts_tasks":false}} {"tool":"send_message","args":{"room":"project","to":"claude-backend","kind":"question","content":"Does the endpoint return user_id?"}} ``` Use a name unique among live sessions in that room. `get_room_status.participants` shows named participants, their declared delivery mode and task acceptance. These are declarations, not proof that a model has read a message. An inbox recipient must register at least once before messages can be queued for it while offline. Messages have a server-generated ID, sender, recipient, kind and conversation ID. Use `in_reply_to` when replying; the service validates the parties and room and retains the conversation ID. `poll_messages` returns pending/read messages for your participant. `get_conversation` shows your sent/received messages and states. Only the recipient can call `acknowledge_message` with `read`, `handled` or `failed`; terminal receipts cannot be reversed. `handled` means the message was processed, not necessarily that the whole project task is finished. A clarification can handle one message while the larger task remains blocked. The inbox, participant names and receipts survive service restarts. Names are reclaimed on reconnect after the old session disconnects, while live duplicates are rejected. History is capped at 512 messages and retained for seven days; expired entries are removed when sending new messages. A full history returns an error rather than evicting recent messages. Registration is capped at 512 names. These names follow the shared service's existing local trust model; they are not new authentication identities or separate security tenants. Addressed conversations currently work between clients of the **same HTTP service**. Existing P2P memories and tasks remain available; addressed inboxes are not yet replicated across remote nodes. ## Claude Code Channels Configure a stdio bridge that connects to the HTTP service, rather than opening a second buddies database. For example, add this entry to the MCP configuration Claude Code uses: ```json { "mcpServers": { "buddies-channel": { "command": "/absolute/path/to/buddies", "args": ["channel", "--url", "http://127.0.0.1:8080", "--room", "project", "--name", "claude-backend"] } } } ``` Start the interactive session with the custom development channel enabled: ```sh claude --dangerously-load-development-channels server:buddies-channel ``` Claude Code Channels are a research preview. The development flag skips the plugin allowlist for this entry and presents an interactive confirmation; it does not bypass organization policy or tool approvals. Use the trusted local bridge you built. An ordinary MCP registration without channel activation does not deliver events to Claude's context. See the [official Channels reference](https://code.claude.com/docs/en/channels-reference). The bridge declares `claude/channel`, negotiates up to MCP 2025-11-25, and emits `notifications/claude/channel` over stdio. It automatically joins/registers, polls the durable inbox once per second and forwards tools to the service using the same participant session. It also delivers delegated tasks, status updates and file-conflict notifications. Room/participant membership is owned by the bridge. It does not acknowledge inbox messages on transport delivery: Claude must use the receipt tool. Unhandled messages can be delivered again after bridge restart; inspect conversation history before repeating actions that may have side effects. Transient status/conflict events have a bounded buffer; durable inbox messages remain available if transient notifications are lost. ## Managed Codex conversation For automatic reception, run an App Server conversation: ```sh buddies agent --room project --name codex-client --cwd /path/to/repo \ --url http://127.0.0.1:8080 --sandbox workspace-write ``` The default sandbox is `read-only`; select `workspace-write` for implementation. Codex must be installed and signed in. `--codex-bin` selects the real executable when PATH contains a wrapper. `--model` is optional. The command prints its Codex thread ID; use `--thread-id ID` to resume it after restarting. `--once` exits after one completed turn. One thread receives successive messages, one new message per turn. Questions, requests and answers queue for the next turn. `update` messages in the active conversation steer the current turn; reference an earlier message with `in_reply_to`. Unrelated updates queue. A confirmed steering rejection queues the update for the following turn; an uncertain dispatch is failed rather than repeated. The runtime records `read` when it consumes an inbox entry, before dispatching to App Server, and `handled` after turn completion. A runtime read receipt therefore does not prove the model has already reasoned about the message. It sends the final response back to question or request senders unless the model already replied with a communication tool. Failed turns produce failed receipts. A read message found after restart is marked failed rather than automatically repeating an ambiguous interrupted execution; review possible effects before resending. The model gets a bounded set of communication tools for the configured room, including addressed replies, history and background worker delegation. Dynamic tools require App Server's experimental API capability. Interactive approval/input requests are unavailable in this unattended runtime. A task needing them must report the limitation. The runtime and its subprocesses stop on shutdown. This opens or resumes a **managed conversation**; it does not take over an unrelated Codex CLI already open in another terminal. A normal Codex MCP session can use the same messages with explicit polling while active. Attaching automatic delivery to an existing interactive UI requires that UI's integration surface. See [Codex App Server](https://learn.chatgpt.com/docs/app-server). ## Collaboration protocol 1. Identify the shared goal, your responsibility and dependencies. 2. Ask the responsible participant before changing a shared contract. 3. Read the relevant conversation before acting on a reply. 4. Reply to questions/requests or explain the blocker; mark the message's outcome. 5. Apply answers to the existing task and verify the effect. 6. Treat status updates as context. Avoid acknowledgement ping-pong. For a manual client, check the inbox before work, at dependency changes and before finishing. These instructions improve active turns; they cannot wake an idle client without host support. ## Verification ```sh cargo build --locked PYTHONDONTWRITEBYTECODE=1 python3 scripts/smoke_collaboration.py ``` The deterministic test exercises actual service/bridge processes and an App Server fixture: a question triggers clarification, the answer continues the same thread and changes an artifact, and an update steers active work. It also checks task delivery, receipt authorization and inbox/history persistence after restart. It does not establish the behavior of a particular real model or demonstrate that Claude Code's channel has been enabled in a user's interactive session. An opt-in check uses the signed-in Codex account, consumes model usage, and tests real dynamic tool calls plus retained context against a simulated peer: ```sh PYTHONDONTWRITEBYTECODE=1 python3 scripts/smoke_live_codex.py --codex-bin /absolute/path/to/codex ``` For a real-client acceptance check, give Claude responsibility for the API contract and the managed Codex participant responsibility for its consumer in a temporary repository. Require clarification about a missing field. Completion requires the question, reply, retained context and a verified implementation, with no human relaying messages. Record outcomes and tool/turn timestamps, not just delivery. --- # Automatic Codex workers A normal MCP connection does not guarantee that the host starts an agent turn when it receives `notifications/buddies/taskArrived`. Start an executor to handle tasks without asking the interactive agent to poll: ```bash buddies worker --agent codex --room my-project --cwd /path/to/repo ``` The worker connects to the running buddies HTTP service, joins by name, and polls its durable queue automatically. It uses Codex App Server to start a separate thread for each task and submits the final result. Codex must already be installed and signed in. Use `--codex-bin /absolute/path/to/codex` if PATH uses a wrapper. This does not wake or take over an existing interactive Codex conversation. `--sandbox workspace-write` is the default; use `--sandbox read-only` for review or testing. The worker runs without interactive approvals and fails requests that require user input. `--model` is optional; otherwise Codex uses its configured model. Only start a worker in a room whose delegated work you intend to execute in that repository. The child Codex process has the buddies MCP connector disabled to avoid recursively accepting tasks. Call `delegate_task` from a room member as usual: live workers are preferred over interactive local agents. Use `target_agent: "codex"` (or the stable worker ID from `get_room_status.workers`) to require a worker. With `background: true`, the call returns `task_id` immediately; call `get_task_status({room, task_id})` for its state and outcome. Without `background`, the call waits and returns the output to the requesting agent. The timeout is the whole task deadline, including queue time (1–300 seconds). Worker routing currently handles delegations submitted to that same buddies service; incoming P2P tasks retain their existing MCP polling path. Worker tasks have durable `queued`, `running`, `completed` and `failed` states. The server commits an exclusive claim before delivery; claim retries return the same task and completed results cannot be overwritten. Queued tasks and outcomes survive MCP disconnects and server restarts. A worker renews its running lease; expired leases and deadlines fail the task. An interrupted execution is never automatically repeated because it may already have changed files. Failure is not a rollback of side effects. Finished tasks are retained for seven days; the queue is bounded to 128 unfinished tasks and 512 total records. The worker keeps a private identity and execution/result journal under `~/.local/share/buddies-workers/` by default, separate from the server database. `--data-dir` selects an explicit worker directory; only one process may open it. Keep this directory to retain identity and retry unacknowledged results after a restart. Its identity is bound to the server URL, room and repository. `--once` exits after one acknowledged task. The monitor labels these participants `worker`. For continuous operation, adapt [`contrib/systemd/buddies-worker.service`](https://github.com/iltumio/buddies/blob/main/contrib/systemd/buddies-worker.service) and install it as a user service. The service terminates the execution process group on shutdown. The HTTP service's existing loopback/trust assumptions also apply to its `/worker` endpoint; worker credentials identify their own claims. --- # Room orchestrator `buddies orchestrator` explicitly starts one persistent coordinator for a room. Running the ordinary buddies service still starts no model. The coordinator owns the goal and assignment graph; specialists retain their own Codex conversations and write in separate git worktrees. Start the HTTP service, then run against a **clean, committed** repository: ```sh BUDDIES_TRANSPORT=http buddies buddies orchestrator --room project --repo /path/to/repo \ --model YOUR_MODEL --goal 'Implement and verify the producer/consumer contract' \ --allow-path src/producer --allow-path src/consumer \ --check 'cargo test --locked' --max-active-specialists 2 ``` `--model` is optional and otherwise uses Codex's configured model. Codex must be installed and signed in; `--codex-bin` can select its real executable. `--url` selects the shared HTTP service. Paths are explicit repository-relative files or directories, without globs, `.` or parent components. Repeat `--allow-path` and `--check` for additional paths and shared checks. The stable participants are `orchestrator`, `specialist-a`, and `specialist-b`. The model may create additional named specialists only for independent responsibilities, with at most 16 registered roles. The service rejects live duplicate names. All participants use addressed inboxes on the same HTTP service; this command does not replicate assignments across P2P nodes. ## Assignment protocol The dynamic `orchestrator_control` tool proposes state transitions. The scheduler validates actor identity, ownership, dependencies and revisions before accepting them. The coordinator can `assign`, `revise`, `cancel`, `recover`, `inspect` and `complete`. Specialists can `accept`, `checkpoint`, `report`, `fail` and `heartbeat` their own assignments. `inspect` is available to every role. Each assignment stores an ID, owner, revision, owned paths, dependencies, deadline, acceptance, worktree, baseline and result evidence. Dependencies must already exist, so the graph remains acyclic. Shared paths have one explicit owner. Reuse that assignment through revisions when its contract changes. The states are `queued`, `running`, `waiting_for_input`, `completed`, `failed` and `cancelled`. A report does not complete an assignment: the runtime waits for the turn to finish, checks all changed paths, runs its reported acceptance checks and integrates the patch. Only verified completion releases dependencies. `handled` inbox receipts still mean that a message was processed. Specialists must accept each revision before reporting its result. Results from older revisions remain unverified evidence. Revising a producer also increments and invalidates its dependent assignments; unrelated assignments keep their revision, worktree and thread. Material changes to active work require a recorded checkpoint and completion of the active turn before revision or cancellation. A new revision gets a fresh worktree; earlier effects remain inspectable. Questions and answers travel through the durable inbox and resume the same specialist thread. An idle specialist can answer a clarification in a read-only turn after its assignment completes. Messages queue while a turn is busy; related `update` messages can steer the active turn. A confirmed steering rejection queues the message; uncertain dispatch stops execution rather than replaying it. Ordinary transport status does not wake the model. `heartbeat` updates stored activity without requesting a coordinator decision. ## Integration and recovery State lives under the repository's common git directory: `buddies-orchestrator//state.redb`. A transactional database lock prevents two local coordinators from owning that state. `state.json` is an atomic, reviewable snapshot, not an editable configuration. This directory also retains specialist worktrees, per-revision patches, acceptance logs and the integration worktree. Internal commits occur only in the detached integration worktree; repository hooks are disabled for the runtime's git operations. The coordinator completes the goal only after every required assignment is verified and specialists are idle. The runtime then runs the user's `--check` commands on the combined changes, validates the authorized scope again, and applies `verified.patch` to the original checkout. It does not commit that checkout. Its HEAD and cleanliness must still match the starting repository; otherwise the patch remains available for inspection and integration. Restart with the same command and add `--resume`. Configuration and aggregate usage persist. Threads resume using their saved IDs. Interrupted work becomes uncertain and cannot run again until the coordinator inspects effects and records an explicit `recover` decision. Read inbox messages from an interrupted dispatch become failed receipts. Completed goals return their stored result without starting Codex. An interrupted integration/publication can require manual inspection of retained worktrees, the saved integration HEAD, and `verified.patch`. A saved publication boundary with an uncertain outcome refuses automatic resume. Do not edit the state snapshot or resend work without reviewing effects. Acceptance failures before publication leave the checkout unchanged and can be addressed by revising the relevant assignments. These worktrees and scope checks guard integration and coordination; they are not a security boundary for hostile code. Reported acceptance commands execute with the local user's permissions in an isolated worktree. Use a repository and goals you trust, as for the existing managed-agent/worker runtimes. ## Budgets Defaults are two active specialist turns, 64 model dispatches, 1 MiB of generated text, one hour of aggregate running time, dependency depth eight and 120 seconds per acceptance command. Configure them with `--max-active-specialists`, `--max-model-calls`, `--max-output-bytes`, `--max-elapsed-secs`, `--max-depth` and `--check-timeout-secs`. `--debounce-ms` defaults to 1000 and combines bursts before dispatch. Specialist turns waiting for input release an active slot, while their assignment stays associated with its owner. The call budget counts host `turn/start` and `turn/steer` dispatches, including failed/uncertain dispatches. It is not a billing/token measurement of internal model requests. Output tracks generated reply/reasoning text and tool arguments. Time and usage do not reset on resume; budget exhaustion stops subprocesses and retains evidence. Graphs, role counts, revisions, input text, patches and check logs are also bounded. The App Server's native multi-agent delegation and autonomous goal continuations are disabled so specialist creation and turn dispatch stay with the scheduler. ## Verification ```sh cargo test --locked --all-targets cargo build --locked PYTHONDONTWRITEBYTECODE=1 python3 scripts/smoke_orchestrator.py ``` The deterministic experiment uses the real HTTP service, durable inbox and git worktrees with an App Server fixture. A consumer asks about a missing field, receives a recorded answer in the same thread, then accepts a material contract change after an active-turn checkpoint. Its revision-two result passes the shared check; the producer remains on revision one. The test also checks stale evidence, thread reuse, single dispatch of each revision, publication, completed resume, and interrupted recovery without duplicate execution. This validates the runtime protocol. It does not measure a real model's cost or compare completion rates with direct peer coordination. That comparison remains an opt-in acceptance experiment using the same models and task described in [the design](https://iltumio.github.io/buddies/orchestrator-design.md). --- # Task delegation Agents can delegate work to each other across the P2P network. Alice's agent can ask Bob's agent to run tests, check a file, or do anything — and get the result back as if it happened locally. ```mermaid sequenceDiagram participant A as Alice's Agent participant SA as buddies (Alice) participant SB as buddies (Bob) participant B as Bob's Agent A->>SA: delegate_task("Run the test suite and report failures") SA-->>SB: gossip → TaskRequest B->>SB: poll_pending_tasks() SB->>B: task: "Run the test suite and report failures" Note over B: Bob's agent executes the task B->>SB: submit_task_result(task_id, success, "All 42 tests passed") SB-->>SA: gossip → TaskResponse SA->>A: { status: "completed", output: "All 42 tests passed" } ``` The delegator's `delegate_task` call **blocks** until a result comes back (or the timeout expires), so from the agent's perspective it feels like a local tool call. On the other side, the receiving agent uses `poll_pending_tasks` (with long-polling support) to pick up work, does whatever it needs to do, and calls `submit_task_result` to send the answer back. ### Push notifications (HTTP transport) When running with `BUDDIES_TRANSPORT=http`, buddies automatically pushes real-time notifications to the connected MCP client whenever a task arrives via P2P gossip. The notification is sent as an MCP `CustomNotification` over the SSE stream: - **Method**: `notifications/buddies/taskArrived` - **Params**: `{ task_id, source_peer, room, description, timestamp, timeout_secs }` The MCP client receives the notification, but must explicitly deliver it to the model and start or continue an agent turn. A successful transport write does not prove the model read it. `poll_pending_tasks` remains available for clients that do not handle custom notifications. Local participants must explicitly declare `accepts_tasks: true` through `register_participant` before receiving delegations. For ongoing Claude Code/Codex conversations, see [Interactive collaboration](https://iltumio.github.io/buddies/collaboration.md). `buddies channel` bridges the shared HTTP service into Claude Code Channels; `buddies agent` runs a managed Codex conversation that retains its thread across messages and can accept updates during an active turn. Both are opt-in commands; starting the MCP service alone does not launch a model. --- # Memory and skills ## Memory types When storing a memory, tag it with a kind: - **decision** — "We decided to use JWT for auth" - **implementation** — "Auth middleware lives in `src/middleware/auth.rs`" - **context** — "The API contract requires a `user_id` field in all responses" - **skill** — "Run `cargo test -- --nocapture` to see test output" - **status** — "Auth module is 80% done, blocked on the DB migration" ## Skill sharing Skills are reusable, content-addressable knowledge entries that agents can publish, discover, and vote on across the P2P network. Unlike memories (which capture what happened), skills capture how to do things — commands, procedures, workflows. Each skill is identified by a SHA-256 hash of its content (title + body + tags), so identical skills published by different peers are automatically deduplicated. Peers vote on skills to surface the best ones. ### Signed skills Skills are **digitally signed** using your configured identity (GPG or SSH). The signature is embedded directly in the skill entry and persists in storage, so any peer can verify the author’s identity at any time — not just at the moment of receipt. - When you publish a skill, buddies signs it with your local signer - When a peer receives a skill, the embedded signature is verified before storing - Skills with invalid signatures are rejected - The `signed_by` field (e.g. `gpg:ABC123` or `ssh:ssh-ed25519 ...`) is returned in search results and skill lookups This is separate from the transport-level P2P message signatures — skill signatures prove *who authored the content*, while message signatures prove *who sent the gossip message*. ```mermaid sequenceDiagram participant A as Alice's Agent participant SA as buddies (Alice) participant SB as buddies (Bob) participant B as Bob's Agent A->>SA: publish_skill("Deploy to staging", "Run ./deploy.sh --env staging") SA->>SA: sign skill with local identity SA-->>SB: gossip → SkillPublished (signed) SB->>SB: verify signature → store B->>SB: search_skills("deploy") SB-->>SA: gossip → SkillSearchRequest SA-->>SB: gossip → SkillSearchResponse SB->>B: results ranked by votes (with signed_by) B->>SB: vote_skill(hash, +1) SB-->>SA: gossip → SkillVoteCast ``` Skills support versioning via `parent_hash` — publish an updated skill referencing the previous version's hash to create a revision chain. Votes require a signing identity. SSH voters are keyed by the public key without its comment; GPG voters must use a full fingerprint. A peer cannot choose another voter's name, and repeating a vote replaces that identity's previous vote. Search responses expose `rank_source: "local_verified_votes"`: ranking uses votes validated by this instance, not an aggregate supplied by a remote peer. Consequently, rankings can differ while peers have received different sets of votes. **Upgrade note:** legacy votes were stored without a verifiable identity binding. They remain in the database for inspection but are excluded from ranking after this update. Cast votes again to populate the new verified-vote table. Memories and skills are retained. Because skill storage is keyed by content hash, publishing identical content into another room now returns an error instead of moving the existing skill between rooms. ## Distributed search No data is replicated unless explicitly stored. Peers only share what matches your query. ```mermaid flowchart LR Agent -->|search_memory| Local[(Local redb)] Agent -->|search_memory| Gossip{gossip broadcast} Gossip -->|SearchRequest| PeerB[(Peer B store)] Gossip -->|SearchRequest| PeerC[(Peer C store)] PeerB -->|SearchResponse| Agg[Aggregate & rank] PeerC -->|SearchResponse| Agg Local --> Agg Agg -->|top results| Agent style Gossip fill:#f59e0b,color:#000 style Agg fill:#10b981,color:#000 ``` --- # Repository awareness `watch_repo({repo_path, room, repo_name?})` starts a filesystem watcher on a local git repository. Join the room first. File-change events are debounced by roughly 1 second and then buddies shells out to `git` to determine what actually changed, so `.gitignore`'d files and build artifacts never get broadcast. Git does the filtering for you. The watcher ignores read-only access events and `.git` metadata churn. It also reconciles against Git every 2 seconds, which clears stale dirty state after a commit even if the filesystem notification was missed. If a Git command or file read fails, that scan is skipped and the last known state is retained rather than broadcasting an empty or misleading update. Each changed file is broadcast to the room as a `FileActivity` message carrying a unified diff against `HEAD`, capped at 64 KiB. The message is signed when a local signer is configured. Receivers bind the activity's `author` to the identity that joined under that peer name; a different signer cannot publish activity, status updates, or leave messages as that peer. Receivers keep only the latest diff per `(repo, file, peer)` triple, and entries expire after 24 hours. - **check_file_activity** should be called before editing a file in a shared repo, to see which peers have touched it recently. - **get_peer_diff** returns a peer's full diff for a specific file so you can review exactly what changed. - When a peer changes a file you've also modified locally, buddies proactively pushes a `notifications/buddies/fileConflict` `CustomNotification` with instructions to reconcile before you continue editing. Its payload includes the repo/path plus branch, change kind, timestamp, and added/removed line counts for both the peer change and the nested `local` change. - Watching the same canonical repo path twice is idempotent. The response reports the repo name and room of the watcher that is actually active; call `unwatch_repo` before moving that path to another room. ```mermaid sequenceDiagram participant A as Alice's Agent participant SA as buddies (Alice) participant SB as buddies (Bob) participant B as Bob's Agent A->>SA: watch_repo({repo_path, room: "feature-a"}) B->>SB: watch_repo({repo_path, room: "feature-a"}) Note over A: Alice edits src/auth.rs SA->>SA: debounce ~1s → git status + git diff HEAD SA-->>SB: gossip → FileActivity (signed) SB->>SB: verify + store (24h TTL) Note over SB: Bob also has src/auth.rs dirty SB->>B: notifications/buddies/fileConflict B->>SB: get_peer_diff("src/auth.rs", "alice") SB->>B: Alice's full diff ``` Git remains the source of truth — buddies never writes to your working tree. Only uncommitted changes are broadcast, watchers stop when the process exits, and late joiners see activity from the moment they join, not before. **Privacy warning**: watching a repo shares source-code diffs with everyone in the room. Only watch repos in rooms with `require_signed=true` and an identity whitelist configured (see [Identity trust model](https://iltumio.github.io/buddies/reference/identity.md)). --- # MCP tools | Tool | Description | |------|-------------| | **join_room** | Join a named room. Returns a ticket for others to connect. | | **leave_room** | Leave a room. | | **store_memory** | Save a memory and broadcast it to every peer in the room. | | **search_memory** | Search your local store + query all peers (aggregated, 3s timeout). | | **list_memories** | List local memories with optional filters. | | **notify_peers** | Broadcast a status update ("I just finished the auth module"). | | **get_room_status** | See who's in the room and what they're working on. | | **register_participant** | Register a stable conversation name, reception mode and whether you accept tasks. | | **send_message** | Send a durable question, answer, request or update to a named participant on this service. | | **poll_messages** | Read your inbox without marking messages handled. | | **get_conversation** | Read your conversation history and receipt states. | | **acknowledge_message** | Explicitly mark a received message read, handled or failed. | | **list_rooms** | List all rooms you've joined. | | **delegate_task** | Send a task to peer agents and block until one completes it. | | **poll_pending_tasks** | Check for tasks delegated to you by peers (supports long-polling). | | **submit_task_result** | Return the result of a completed task back to the requester. | | **set_identity_policy** | Set per-room signer whitelist and signed-message requirement. | | **add_whitelisted_identity** | Add one signer identity (`gpg:` or `ssh:`) to a room policy. | | **get_identity_policy** | Read current room identity policy and local signer identity. | | **publish_skill** | Publish a digitally signed, content-addressable skill and broadcast to all peers. | | **search_skills** | Search skills locally + across all peers, ranked by votes. | | **vote_skill** | Upvote (+1) or downvote (-1) a skill. Votes propagate to all peers. | | **get_skill** | Retrieve a specific skill by its content hash. | | **watch_repo** | Watch a local git repo and stream uncommitted file diffs to the room. | | **unwatch_repo** | Stop watching a repository. | | **check_file_activity** | See which peers recently changed which files in a watched repo. | | **get_peer_diff** | Read a peer's latest diff for a specific file. | ## Background task status `get_task_status({room, task_id})` returns the state and outcome of a background worker task. Use it after `delegate_task` with `background: true`. See [workers](https://iltumio.github.io/buddies/guide/workers.md). The connected server's MCP `tools/list` response supplies the current input schemas. Use those schemas when constructing calls; this reference describes their purpose. --- # Configuration | Environment variable | Default | Description | |---------------------|---------|-------------| | `BUDDIES_USER` | OS username | Your display name in rooms | | `BUDDIES_AGENT` | `unknown-agent` | Which agent you're using | | `BUDDIES_DATA_DIR` | `~/.local/share/buddies` | Where local memories are stored | | `RUST_LOG` | `warn` | Log level (logs go to stderr, never pollutes MCP stdio) | | `BUDDIES_TRANSPORT` | `stdio` | Transport mode: `stdio` (default, for MCP clients that spawn the process) or `http` (standalone HTTP server) | | `BUDDIES_PORT` | `8080` | HTTP listen port (only used when `BUDDIES_TRANSPORT=http`) | | `BUDDIES_HOST` | `127.0.0.1` | HTTP bind address (only used when `BUDDIES_TRANSPORT=http`) | | `BUDDIES_HEARTBEAT_SECS` | `10` | Presence announcement interval | | `BUDDIES_SUSPECT_SECS` | `30` | Elapsed time before a peer becomes unreachable | | `BUDDIES_OFFLINE_SECS` | `90` | Elapsed time before a peer becomes offline | | `BUDDIES_MCP_IDLE_SECS` | `300` | HTTP session inactivity limit; expired clients must initialize again | | `BUDDIES_SIGNER` | `git` | Signing identity source: `git`, `none`, `gpg`, `ssh`, `generated` | | `BUDDIES_GPG_KEY_ID` | unset | GPG key ID when `BUDDIES_SIGNER=gpg` (or use `BUDDIES_SIGNING_KEY`) | | `BUDDIES_SSH_PRIVATE_KEY` | unset | SSH private key path when `BUDDIES_SIGNER=ssh` | | `BUDDIES_SSH_PUBLIC_KEY` | inferred | SSH public key value or path when `BUDDIES_SIGNER=ssh` | | `BUDDIES_SIGNING_KEY` | unset | Generic fallback for `BUDDIES_GPG_KEY_ID` or `BUDDIES_SSH_PRIVATE_KEY` | ### Startup identity options - Use existing git signing identity (default): - `BUDDIES_SIGNER=git` - Reads `git config user.signingkey` and `git config gpg.format` - Provide explicit key at startup: - GPG: `BUDDIES_SIGNER=gpg BUDDIES_GPG_KEY_ID= buddies` - SSH: `BUDDIES_SIGNER=ssh BUDDIES_SSH_PRIVATE_KEY=~/.ssh/id_ed25519 buddies` - Generate identity on first start and reuse it: - `BUDDIES_SIGNER=generated buddies` - Generates `identity_ed25519` in `BUDDIES_DATA_DIR` and uses it for signing ## Resource limits and shutdown Distributed searches accept at most 30 seconds; task delegation accepts 1–300 seconds. Long polling is capped at 30 seconds. Each outgoing request registry allows 128 concurrent requests, and merged searches retain at most 50 results. Cancellation and broadcast errors release request registrations immediately. Database work runs outside the async runtime with four concurrent worker slots. Signing and verification use at most four child processes, with a 10-second queue/execution deadline; timed-out or cancelled commands are terminated. Configured signing failures are errors, never an automatic unsigned fallback. SIGINT/SIGTERM stops HTTP admission, cancels MCP sessions and notifications, and allows up to five seconds for HTTP connections to finish before stopping watchers, room receivers, and the Iroh router. The stdio transport also cleans up on EOF. --- # Identity and trust - Outbound gossip messages are signed when a local signer is configured. - Published skills are signed at the content level (signature embedded in the skill entry). - Per-room policies can require signed messages and/or enforce whitelisted signer identities. - Identity label format is: - `gpg:` - `ssh:` - If a room has whitelist entries, messages from non-whitelisted identities are dropped. - If `require_signed=true`, unsigned messages are dropped. - Peer-scoped actions must come from the identity that joined under that display name. A different signer cannot impersonate that peer in file activity, status, or leave messages. - Incoming skills with invalid hashes or embedded signatures are rejected, including search results. Signed-only room policies also require an embedded skill signature. - Remote searches are always scoped to the receiving room. Memory/skill/task payloads claiming a different room are rejected. - Signed messages carry a nonce and a `sent_at` timestamp: receivers drop signed messages older than 10 minutes (clock-skew tolerant) and messages whose nonce was already seen, so captured messages cannot be replayed. - GPG identities should be whitelisted by full fingerprint — short key ids are collision-prone. Example policy setup: ```json { "tool": "set_identity_policy", "args": { "room": "feature-a", "identities": [ "gpg:ABC123DEF456", "ssh:ssh-ed25519 AAAAC3Nza... alice@laptop" ], "require_signed": true } } ``` --- # Architecture ```mermaid graph TB subgraph "Each developer's machine" Agent[AI Agent] MCP[MCP stdio / HTTP] Server[BuddiesServer] Storage[(redb)] Agent <-->|JSON-RPC| MCP MCP <--> Server Server <--> Storage end subgraph "P2P Layer" Iroh[iroh Endpoint
QUIC + NAT traversal] Gossip[iroh-gossip
HyParView + PlumTree] end Server <--> Iroh Iroh <--> Gossip Gossip <-.->|"Room: feature-A
(TopicId = SHA256)"| OtherPeers[Other buddies peers] style Agent fill:#8b5cf6,color:#fff style Gossip fill:#f59e0b,color:#000 style Storage fill:#3b82f6,color:#fff style OtherPeers fill:#10b981,color:#000 ``` - **Transport**: MCP over stdio (default) or streamable HTTP — stdio for clients that spawn the process, HTTP for standalone deployment - **MCP**: [rmcp](https://github.com/modelcontextprotocol/rust-sdk) 3.x — server tools over stdio or streamable HTTP - **Networking**: [Iroh](https://iroh.computer) 1.x — QUIC connections with NAT hole-punching and relay fallback - **Gossip**: [iroh-gossip](https://github.com/n0-computer/iroh-gossip) 0.101.x — epidemic broadcast trees (HyParView + PlumTree) - **Storage**: [redb](https://github.com/cberner/redb) 4.x — embedded key-value store, single file, zero config - **Hashing**: [sha2](https://github.com/RustCrypto/hashes) 0.11.x — room topics and content fingerprints - **Wire format**: [postcard](https://github.com/jamesmunns/postcard) — compact binary serialization for gossip messages Rooms map to gossip topics via deterministic SHA-256 hashing. Same room name = same topic = same swarm. Peers discover each other through Iroh's relay infrastructure and direct QUIC hole-punching. ## Persistent orchestration The [orchestrator](https://iltumio.github.io/buddies/orchestrator.md) coordinates participants on one HTTP service. Its scheduler persists assignments, revisions, thread IDs and verification evidence under the repository common git directory. Specialists work in detached worktrees; only verified results release dependencies and the shared acceptance checks gate publication to the original checkout. --- # Documentation for AI agents Use this guide when connecting an agent to buddies, coordinating work in a room, or diagnosing delivery and recovery. ## Read only the context you need - Setup: [getting started](https://iltumio.github.io/buddies/guide/getting-started.md), [MCP clients](https://iltumio.github.io/buddies/guide/clients.md) and [configuration](https://iltumio.github.io/buddies/reference/configuration.md). - Questions, replies and steering: [interactive collaboration](https://iltumio.github.io/buddies/collaboration.md). - Independent background tasks: [workers](https://iltumio.github.io/buddies/guide/workers.md) and [delegation](https://iltumio.github.io/buddies/guide/delegation.md). - Goals, ownership, revisions and recovery: [orchestrator](https://iltumio.github.io/buddies/orchestrator.md). - Shared edits: [repository awareness](https://iltumio.github.io/buddies/guide/repository-awareness.md) and [identity policy](https://iltumio.github.io/buddies/reference/identity.md). - Tool arguments: discover schemas through MCP `tools/list`; use the [tool reference](https://iltumio.github.io/buddies/reference/tools.md) for purpose and routing. ## Join and communicate 1. Connect to the configured MCP service and call `join_room`. Confirm the returned room and your `agent_id`. 2. For addressed communication, register a stable participant name with your actual delivery mode and task acceptance. Confirm registration before sending work to that name. 3. Read conversation history before acting on a reply or a redelivered message. Reply using `in_reply_to` so the service retains the conversation. 4. Record `read` when consumed, then `handled` or `failed` when the message processing ends. A handled message is not proof that its project assignment passed acceptance checks. 5. Before editing shared files, inspect file activity and relevant peer diffs. Resolve the shared contract with its owner and verify the resulting implementation. ## Completion and recovery For worker tasks, inspect `get_task_status` and the reported outcome. For orchestrated goals, completion requires the scheduler's verified assignment results and the shared checks. An interrupted run may already have changed files: inspect retained effects and follow the runtime's recovery procedure before dispatching work again. MCP notifications require host support to start or continue a model turn. Polling while active is available to manual clients; it cannot wake an idle model. Addressed conversations and orchestration currently route through one shared HTTP service. P2P rooms share knowledge and support their own delegation path. ## Fetch plain Markdown ```sh curl -fsSL https://iltumio.github.io/buddies/llms.txt curl -fsSL https://iltumio.github.io/buddies/orchestrator.md curl -fsSL https://iltumio.github.io/buddies/llms-full.txt ``` These are static exports of the same published Markdown used to build the HTML site. They require no JavaScript, account or external model service. `llms.txt` is a discovery index; it does not guarantee that a particular client automatically discovers or consumes it. --- # Development ```bash cargo test --all-targets # unit and integration-style tests cargo fmt --check # formatting cargo clippy --all-targets -- -D warnings # lints ``` CI runs all three, a Rust 1.91 compatibility check, and MCP transport/shutdown smoke tests on every pull request and on pushes to `main`. Warnings are errors, so run `cargo clippy` locally before opening a PR. Dependabot checks Cargo and GitHub Actions weekly; a separate weekly job runs `cargo audit`. To run the Linux transport test locally (requires Python 3, Git, and a debug build): ```bash cargo build --locked python3 scripts/smoke_mcp.py ``` Signature regression tests require `ssh-keygen`. All smoke-test data and watched files are created in temporary directories. Two things to know before changing the wire format in `src/protocol.rs`: - `P2PMessageBody` is serialized with postcard, which encodes enum variants **by index**. Adding a variant anywhere but the end silently breaks compatibility with peers running an older build. Always append. - Gossip frames are capped at 256 KiB (`GOSSIP_MAX_MESSAGE_SIZE` in `src/node.rs`). Exceeding the limit is fatal to the connection on both send and receive, so any new payload field needs an explicit size cap — see `FileActivityEntry::validate_received` in `src/activity.rs` for the pattern. Fields that arrive from peers are attacker-controlled. Validate them at receipt (length, timestamps, NUL bytes) rather than trusting the sender, and do it *after* signature verification so unverified traffic can't reach the validation path. ## Documentation Use Node.js 22 or newer. ```sh npm ci npm run docs:dev npm run docs:build npm run docs:preview ``` VitePress 1.6 uses a patched Vite 6 override (compatible with its Vue plugin) and a patched KaTeX override for Mermaid. Keep these overrides until the upstream dependency ranges include patched releases. The build generates HTML, per-page Markdown, `llms.txt` and `llms-full.txt` from the same published pages. It checks links and generated agent exports. Edit the Markdown sources rather than the generated files in `docs/.vitepress/dist`. Historical design/review files under `docs/superpowers` and the repository review are excluded from the public site. Pull requests build the docs without publishing. Pushes to `main` build and deploy through the Documentation workflow to GitHub Pages. ## Automatic releases [release-plz](https://release-plz.dev/docs/config#the-git_only-field) manages versions and `CHANGELOG.md` using the existing `v{{ version }}` tags. Releases are GitHub-only; no crates.io token is required. 1. A push to `main` runs the shared CI checks, then release-plz opens or updates a release PR with the next version and changelog. Conventional Commits drive version selection. CI is explicitly dispatched on the generated branch. 2. Review and merge the release PR. Once CI passes, release-plz creates the tag and a draft GitHub release. 3. The workflow builds Linux x86_64 and ARM64 binaries from that exact tag, attaches both binaries, and publishes the release as latest. This keeps the installer's existing download URLs working. Automation uses the repository's `GITHUB_TOKEN`. In **Settings → Actions → General → Workflow permissions**, enable **Allow GitHub Actions to create and approve pull requests**. No additional PAT or registry secret is needed. The build is invoked as a reusable workflow because tags created with `GITHUB_TOKEN` do not trigger another push workflow. If binary compilation fails, the release stays a draft. Rerun the failed jobs, or manually run the **Build** workflow with `release_tag` set to the existing version tag. Uploads are idempotent. Regular CI can also be run manually through its `workflow_dispatch` trigger. --- # A room orchestrator The next implementation increment described here is now available through `buddies orchestrator`. See [usage, assignment protocol and recovery](https://iltumio.github.io/buddies/orchestrator.md). The baseline implementation section below describes the primitives on which the command was built. A deterministic producer/consumer experiment covers the runtime protocol; a real-model comparison with direct peer coordination remains an opt-in experiment. ## Recommendation Use one opt-in, persistent orchestrator per room. It owns the goal, dependencies, assignments and integration of results. Specialist agents own bounded tasks and keep their execution context. The message/inbox runtime is the foundation, but there is no automatic model launch when the buddies service starts. An orchestrator is useful when work depends on several results or requirements change during execution. Two agents with a clear shared contract can coordinate directly. Adding a model to every status event would increase cost and contention without ensuring better decisions. ## Execution model ```mermaid flowchart TD U[User goal and authorized scope] --> O[Persistent room orchestrator] Q[Durable addressed inbox] --> O O --> S[Deterministic scheduler and assignment state] S --> A[Specialist A: persistent conversation] S --> B[Specialist B: persistent conversation] A --> Q B --> Q O --> R[Integrated result and verification] ``` Separate the scheduler's deterministic responsibilities from the model's choices. The scheduler stores assignment IDs, owner, revision, dependency, deadline and state, and enforces concurrency and resource budgets. The model decides how to break down the goal, resolves ambiguity and proposes assignment changes. Keep `queued`, `running`, `waiting_for_input`, `completed`, `failed` and `cancelled` as assignment states. Message `handled` means processed, not assignment completed. Results must identify the assignment revision they satisfy. A result from an old revision is retained as evidence and does not satisfy a newer requirement. When new information arrives: | Event | Orchestrator action | | --- | --- | | Question or blocker | Supply context or ask the responsible agent/user; resume the same assignment. | | Requirement change | Increment the affected assignment revision, send an addressed update and require acceptance. | | Partial/result message | Validate evidence, update dependencies and release ready assignments. | | Status/heartbeat | Update stored state; invoke the model only if a meaningful decision is needed. | | Executor disappearance | Mark execution uncertain; inspect effects before retrying. | An update can steer an active managed Codex turn. For a material scope change, ask for a checkpoint before replacing/cancelling work. Steering does not undo commands or file changes already performed. An agent spawned for a new task receives the goal, assignment, authorized paths, relevant decisions and completion criteria. Existing specialists receive the delta plus its revision. ## What the current implementation provides - Stable participant names and a durable question/answer inbox with receipts. - A managed Codex thread that receives subsequent messages and supports steering. - A Claude Code Channel bridge for interactive reception. - Existing automatic workers with durable task queues and outcomes. The managed agent can coordinate through addressed messages and submit bounded background worker tasks. These primitives allow experiments with an agent named `orchestrator`, given a concrete coordination task by the user. They do not yet provide a production orchestrator's assignment graph, revision acceptance, automatic specialist spawning, aggregate budget or verified result integration. Existing one-shot worker tasks have no live steering interface; use persistent specialist conversations for tasks that need ongoing negotiation. ## Next implementation increment Add an explicit `buddies orchestrator` command with a room, repository, model, goal, authorized paths and maximum active specialists. Start from a persisted assignment graph and two specialist roles. Keep a stable conversation/thread ID per specialist and reuse it for clarification and requirement updates. Create a new specialist only when the task needs another independent responsibility. Use separate worktrees for concurrent writes and require explicit ownership of shared contracts. The orchestrator integrates changes after the acceptance checks pass. If the client is busy, queue messages by default; reserve steering for input that affects current work. Debounce bursts and summarize relevant changes before invoking the model. Bound model calls, output, elapsed time and recursion depth. The ordinary transport service remains usable without starting any model. ## Acceptance experiment Give two specialists a producer/consumer task with an intentionally missing field. Then change the contract while one specialist is active. Compare direct peer coordination with orchestrated coordination using the same models and task. Success requires all of the following: - The ambiguity is resolved through a recorded question and answer. - The affected specialist accepts the new assignment revision. - Its result implements the revised contract and passes the shared check. - Unaffected work continues without unnecessary restart. - No human relays messages, no duplicate execution occurs, and costs/latency fit the configured budget. Use this experiment to decide whether the extra orchestrator improves completion rate and recovery, rather than counting how often agents talk.