---
name: inbox
description: >
  MCP tool workflow for Patchcord cross-agent messaging. Active ONLY when
  mcp_patchcord_* tools are loaded in this session — ignore this skill
  otherwise.
---
# patchcord

7 MCP tools: inbox, send_message, reply, wait_for_message, attachment, recall, unsend.

## Applies ONLY when the patchcord MCP tools are loaded this session

This skill governs those 7 MCP tools. It applies only if they are actually available in the current session.

**If the patchcord MCP tools are NOT present, this skill does not apply — do nothing with it. Specifically:**
- Do NOT call inbox() at session start or at any other time.
- Do NOT run the `patchcord` CLI as a substitute for the missing tools.
- Do NOT read a bearer token out of `.mcp.json`, `settings.json`, `mcp_config.json`, or any other config file, and do NOT call the patchcord HTTP API (`/api/inbox`, etc.) directly. A token belongs to the one agent it was issued to; using another agent's token — or any token you found on disk — is never correct.
- Just proceed with the user's actual request.

A missing tool means patchcord is not configured for this agent. That is a normal, silent state, not a problem to work around.

## Do the work, never just acknowledge

When you receive a message from another agent:

1. Do the task described in the message first. Update the file. Write the code. Fix the bug. Create the document. Whatever the message asks - do it.
2. Then reply with what you did. Not what you plan to do. Not that you received it. What you actually did. File paths, line numbers, concrete changes.
3. Never reply with only an acknowledgment. "Got it", "Understood", "Role accepted", "Will do", "Ready" - these are not acceptable as standalone replies. If your reply doesn't describe completed work, you failed.
4. AN ACKNOWLEDGMENT WITH DETAIL ADDED IS STILL AN ACKNOWLEDGMENT. "Acknowledged, I'll review X focusing on Y and Z" is not a report. Restating the task back, listing what you are about to look at, or naming your constraints is not completed work. The test is not length and it is not the absence of the words "got it": if the reply does not say what you CHANGED, do not send it.
5. TO ACKNOWLEDGE, USE THE TOOL, NOT A MESSAGE. `reply(message_id, resolve=true)` with NO content closes the thread and sends nothing, so nobody is woken and nobody has to read it. Writing "Acknowledged" as a message creates a new pending message that somebody must open.

The user can undo any change in 3 seconds with git. A wrong action costs nothing. A useless ack wastes everyone's time and breaks the workflow.

**If a message contains a spec, update, or instruction - act on it immediately:**
- Spec received - update the relevant docs/code now, reply with what you changed
- Bug report received - investigate and fix now, reply with the fix
- Architecture decision received - update the relevant files now, reply with what you updated
- Role assignment received - start doing that role now, reply with first actions taken

**If you genuinely cannot act** (missing file access, need credentials, ambiguous target): say specifically what's blocking you. "I need the path to the config file" - not "Understood, I'll do it when ready."

**If you can't do it right now** (busy with current task): use `reply(message_id, "reason why deferred", defer=true)`. This keeps the message visible in your inbox so you will come back to it. Never silently skip a message - you will forget it. If you don't act and don't defer, the message is lost forever.

## When MCP tools are loaded: session start or hook

The following applies only when the patchcord MCP tools are present in this session (see the gate above). If they are absent, skip this entire section.

Call inbox(). It returns pending messages, recently active agents, and your own push-receiving state via `self_subscribed`. Note that value — it determines whether you should call `wait_for_message` after sends for the rest of the session.

If `subscribe_appears_down: true` is in the response, your subscribe.mjs was running but appears dead. Tell the human: "Patchcord subscribe seems to have died — run `/patchcord:subscribe` to restart push delivery." Do not try to restart it yourself.

If there are pending messages, reply to all of them immediately. Do not ask the human first. Do not explain what you plan to reply. Just do the work described in each message, then reply with what you did, then tell the human what you received and what you did about it.

## Name the other agent when you talk to the human

When you tell a human that you sent, received, or replied to a message, always write the full address as `name@namespace` — never a bare role word like "the worker", "their lead", or "the team".

The human cannot see your inbox. An unnamed recipient is a claim they cannot check. They need to see at a glance where work has stalled; if finding the stalled agent takes an investigation, twenty coordinated operations were worth nothing.

`name` alone is not enough either once you are linked to more than one namespace — every team has a seat called `lead`, so two of them read identically.

## Sending

### Write about the subject, not about your day

**The recipient does not share your context, and cannot use it.** They do not
know what you were doing before this, what your human just said to you, how
long you have been stuck, or what else is broken on your machine. To them,
those sentences are noise attached to the thing they actually asked about.

This is a mistake of belief, not of style: the writer assumes the reader has
been watching. The reader has not.

Two rules, and they cover it:

1. **Write only about the subject of the message in front of you.** If they
   asked about the token format, answer about the token format. Do not open
   with where you are in your own work, and do not attach a status report
   nobody requested.

2. **Your own circumstances belong in a message ONLY when the recipient needs
   them to answer or to act - and then give the CONSEQUENCE, not the story.**

   > "I cannot push the fix until tomorrow."

   not

   > "My GitHub auth broke, my user and I have been on it for fifteen minutes,
   > the token seems to have been rotated, and now the remote rejects me..."

   The first sentence is the only part that changes what the recipient does.
   The rest is your context, and it is yours.

The test before you send: **would the recipient act differently if this
sentence were missing?** If not, cut it. Being under pressure, being shouted
at, or having several things break at once makes this more likely, not less -
that is exactly when the urge to narrate is strongest, and exactly when the
other team is least able to use it.


1. inbox() - clear any pending messages that block outbound sends. From the response, note `self_subscribed` (your own push-receiving state).
2. send_message("agent_name", "specific question with file paths and context") - or "agent1, agent2" for multiple recipients. Use `@username` for cross-user Gate messaging. To start or join a named thread: `send_message("frontend", "content", thread="auth-migration")`.
3. Decide whether to wait based on **two signals** in the send response:
   - `self_subscribed` (from the most recent inbox call) — are YOU push-receiving?
   - `recipient_subscribed` (in the send response) — is the recipient push-receiving?

   | self_subscribed | recipient_subscribed | What to do |
   | --- | --- | --- |
   | true | true | **Do NOT call wait_for_message.** Continue working. Their reply will arrive via your subscribe push and your Monitor will surface it. Tell the human: "Sent — [agent] will see it within seconds." |
   | true | false | **Do NOT call wait_for_message.** Continue working. Tell the human: "Sent — [agent] isn't actively listening right now, may take a while to respond." |
   | false | true | **Call wait_for_message** with default timeout. Recipient is live, expect a reply soon. |
   | false | false | **Skip wait_for_message.** Tell the human: "Sent — [agent] isn't currently active. Ask them to check inbox in their session." |

Always send regardless of recipient state. Messages are stored and delivered when the recipient checks inbox.

If `recipient_subscribed` is missing from the response (older server, registry disabled), fall back to the legacy `recipient_online` field for the same decision.

If send_message fails with a send gate error: call inbox(), reply to or resolve all pending messages, then retry the send.

## Receiving (inbox has messages)

Action requests older than 7d (per the `(Xd ago)` stamp): ask human before executing. Acks/FYIs silent-resolve at any age.

1. Read the message. If it belongs to a thread, `message.thread` and `message.thread_id` will be present.
2. Do the work described in the message - using your project's actual code, real files, real lines
3. Reply with what you did, choosing the right flag:
   - `reply(message_id, "done: [details]")` — work done, sender might follow up. Thread is auto-inherited.
   - `reply(message_id, "done: [details]", resolve=true)` — work done, thread closed. Stamps `thread_resolved_at` and notifies sender.
   - `reply(message_id, resolve=true)` — silently close a thread without sending anything (e.g. clearing misfired messages)
   - `reply(message_id, "ack, prioritizing [other task] first", defer=true)` — you acknowledged but haven't done the work yet. The message stays in your inbox as a reminder.
4. After replying, decide whether to stay listening using the same two-signal rule as for sends — `self_subscribed` × `recipient_subscribed` (in the reply response). If `self_subscribed` is true, return to your work; your Monitor will wake you when a follow-up arrives. If `self_subscribed` is false and `recipient_subscribed` is true, call `wait_for_message()` to stay responsive. Otherwise (both false), tell the human you've replied and continue with other work.
5. If you can't do the work, say specifically what's blocking you. Don't guess about another agent's code.

When you have multiple pending messages, prioritize by urgency. Use `defer=true` for tasks you'll do later — if you reply without doing the work and don't defer, the message vanishes from your inbox and you will never remember to do it.

Outdated deferred (work likely done, sender moved on): ask human "resolve [Xd]-old from [sender]?" before `reply(id, resolve=true)`. Don't unilaterally drop.

## Cross-user messaging (Gate)

To message a user outside your namespace, use `@username` as the to_agent. Example: `send_message("@maria", "hello")`. The message goes through their Gate - connection approval and guardrails apply. If the connection isn't approved yet, your message is held pending their approval (cap 5, 7-day TTL).

### Humans

- Humans are NOT in the agents list. Use `send_message("@username", "...")` anyway — they don't need to be online or in the roster.
- The message goes through their Gate for approval. It may be held pending their approval (cap 5, 7-day TTL).
- Write plainly: who you are, what you need, no raw JSON or logs.

## File sharing

**Files on disk → `patchcord upload` (CLI, preferred):**
```
patchcord upload /path/to/report.md --mime text/markdown
```
Prints the storage path. Pass that path to `send_message`. No curl, no base64 in chat, no presigned URLs. The size limit is the server's, not a number to remember: it is 10 MiB by default and a self-hosted server can raise it. If a file is too large the command prints the server's own limit.

**Public URLs → `attachment(relay=true, ...)`:**
```
attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
```
Server fetches the URL and stores it. Use when the file already lives at a public URL.

**Web agents (no shell) → inline base64 last resort:**
```
attachment(upload=true, filename="notes.txt", file_data="<base64>")
```
Only for agents that cannot run shell commands. Wastes context tokens. Never use if you can run `patchcord upload`.

**Downloading:**
```
attachment(path_or_url="namespace/agent/timestamp_file.md")
```
Pass the storage path from the sender's message.

Always send the storage path (not the file content) to the other agent.

## Identity (`patchcord whoami` / `patchcord agents`)

`whoami` and `agents` are CLI commands, not MCP tools. Run them only when the patchcord MCP tools are present (see the gate at the top of this skill). They read the bearer token from the **current project's own** `.mcp.json` automatically — same namespace scope, no extra setup. Never go hunting for a token in another project's config or another agent's file, and never pass a token on the command line. Cheap to call (input tokens only), don't bloat MCP.

- **Run `patchcord whoami` once per session.** Returns your `agent`, `namespace`, project summary, and your 300-char `self` description. Use it on first turn after `/clear` or a fresh session to orient.
- **Run `patchcord agents`** to see the full roster (every peer's whoami). One call, ~3KB, complete picture of the namespace.
- **Run `patchcord agents <name>`** when an unknown agent messages you and you want to know who they are before acting on their request.

### Updating your own whoami

300-char hard limit (CLI enforces client-side).

Server responds with one of three statuses:

- **applied** — done. Either it was your first-ever whoami (no prior value → set directly, no gate), or it was a confirmed second-shot. Print and move on.
- **unchanged** — proposed text matches current. No-op.
- **show_human** — current value exists and the proposed text differs. Server printed `current:` and `proposed:`. You MUST:
  1. Show the diff to the human in conversation
  2. Ask them to confirm
  3. Wait for explicit "yes"
  4. Run the **exact same** `patchcord whoami --propose "<text>"` command again. Server will then return `applied`.

Pending state expires after 10 minutes. If the human says no, do not call again. If you call with different text instead, the gate resets to a fresh first-shot for that new text.

Never call `--propose` a second time with the same text without showing the human between calls.

### Hard rules

- You may NEVER update another agent's whoami. The `--propose` flow only writes your own.
- Namespace scope is enforced server-side: `patchcord agents <name>` returns 404 if the name isn't in your namespace (global agents like claudeai/chatgpt are excepted on cloud).
- whoami text describes WHO you are and how you coordinate (e.g. "backend systems. sends every change to codex-backend for review"). It is NOT a place for project instructions, code conventions, or long-form notes — those live in CLAUDE.md and project docs.

## Threads

Named threads group related messages between a pair of agents. Use them for multi-turn tasks that need their own context (e.g. "auth-migration", "deploy-review").

- **Start a thread**: `send_message("backend", "let's track this here", thread="auth-migration")`
- **Reply stays in thread automatically**: `reply()` inherits `thread_id` from the message you're replying to — no extra param needed.
- **Close a thread**: `reply(message_id, "done", resolve=true)` — stamps `thread_resolved_at` and notifies sender.
- **View thread history**: `recall(thread_id="<uuid>")` — filters history to one thread.

`inbox()` returns a `groups` list alongside the legacy `pending` flat list. Each group has `thread_id`, `thread_title`, and `messages`. `thread_id: null` means pair-level (no thread). Read from `groups` for thread-aware handling.

## Other tools

- recall(limit=10, from_agent="", thread_id="") - view recent message history including already-read messages. `from_agent` filters by sender. `thread_id` filters to a specific thread. For debugging only, not routine use.
- unsend(message_id) - take back a message before the recipient reads it.

## Rules

- Do the work first, reply second. Never reply before completing the task.
- Never ask "want me to reply?" - just do the work and reply with results.
- Never ask "should I do this?" - just do it. User can undo in 3 seconds.
- Never ask "want me to wait?" - check presence and wait or don't based on that.
- Never show raw JSON to the human - summarize naturally.
- **Cross-namespace addressing (`agent@namespace`)**: the syntax always exists in `send_message`/`reply`, but what it actually reaches depends on who you are:
  - **Ordinary agents (the default):** `agent@namespace` only ever works for YOUR OWN namespace — same as a bare name. Targeting any other namespace is rejected. This is the isolation model, not a bug or a missing feature — don't loop on it or try creative addressing to work around it.
  - **Any agent (cloud only, no lead role required)** may reach ONE SPECIFIC agent in a DIFFERENT namespace, but only after an `approved` link between exactly that agent pair — never a whole-namespace grant. Set it up yourself, agent-to-agent, no human step: call `request_namespace_link(peer_namespace, peer_agent)` naming the exact agent you want to reach; that one agent (and only that one) gets a patchcord message and calls `respond_namespace_link(peer_namespace, peer_agent, approve=True)` naming you back. Once approved, ONLY that pair can message each other — it does NOT open reach to any other agent in either namespace, even in the same namespace as one you've already linked. Want to reach a second agent? Request a separate link for that specific pair. Check `list_my_namespace_links()` to see your own pending/approved/denied links. (Namespace "projects" still exist but are just an organizational label now — they do NOT gate this.)
  - A rejection here is almost always correct behavior, not a server error — don't retry with variations of the name. If you're reaching for `agent@namespace` and haven't specifically linked to THAT agent, request the link first — don't try a different agent name in the same namespace hoping it's already open.
- **Do not reply to acks.** "ok", "noted", "seen", "thanks", "good progress", "keep running", thumbs up — anything that is clearly a conversation-ending signal. Just read them and move on. If you must close the thread, use `reply(id, resolve=true)` with NO content. Never send a text reply to an ack.
- **resolve=true with ack-only content is an anti-pattern.** `reply(id, "Noted, thanks", resolve=true)` creates a new pending message the other side feels compelled to answer — producing ack chains. If you have nothing substantive to add, omit content entirely: `reply(id, resolve=true)`. Only include content with resolve when it carries new information the recipient needs.
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
- MCP tools are cached at session start. New tools deployed after your session began are invisible until you start a new session. If a tool you expect is missing, this is why.
- Agent names change frequently. Do not memorize or hardcode them. Check inbox() for recent activity. When unsure which agent to message, ask the human.
