# Handoffs between agents

> Let two agents, say Codex and Claude Code, work on the same plan and pass work to each other, and set up a Stop hook so the other agent notices.

Two agents can work on the same plan, and one can hand something to the other: Codex finishes a step and asks Claude Code to review it, say. A handoff is a comment thread addressed to an agent, so you see it in the plan like any other comment and can answer it yourself.

Only your own agents receive handoffs your agents send. Someone else's Claude Code never picks up work your Codex handed off, even on a plan you share.

## How it works

1. The sending agent calls `hand_off` with the plan, who it's for (`codex`, `claude-code`, or a specific session's id) and what it needs. It can anchor the handoff to a step or any other block.
2. The receiving agent takes it with `wait_for_handoff`. Once taken, no other agent gets it, so two Claude Code windows never both start on it.
3. The receiver does the work and answers in the thread with `reply_comment`, then `resolve_comment` when it's done. The sender reads the answer with `get_feedback`, or waits for it with `wait_for_review`.

## When the other agent notices

planpage can't start an agent that isn't running. Nothing on a server can: an agent only works while it's in a turn. So a handoff reaches the other agent at one of these moments:

| The other agent is… | It sees the handoff… |
| --- | --- |
| Waiting for one | As soon as it arrives, when it called `wait_for_handoff`. Ask it to "wait for handoffs from Codex". |
| Working | On the result of its next planpage call, which lists handoffs waiting for it. |
| Finishing its turn | If you set up the Stop hook below: it's told about the handoff and carries on instead of stopping. |
| Idle at the prompt, or closed | Next time you prompt it, or it calls planpage. |

## Set up the Stop hook

The hook runs when the agent is about to finish its turn. If a handoff is waiting for it, the agent keeps going and picks it up. It tells the agent about each handoff once, so an agent that decides to leave one alone isn't stopped again for it.

The hook signs in with an API token, not the agent's own connection:

1. Create a token in **Your account → Agent connections**. **Read only** is enough, for the workspaces your plans are in.
2. Put it in an environment variable called `PLANPAGE_TOKEN` where the agent runs.
3. Add the hook for your agent.

**Claude Code.** In `~/.claude/settings.json`, merged into any hooks you already have:

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -fsS --max-time 10 -X POST -H \"Authorization: Bearer $PLANPAGE_TOKEN\" \"https://app.planpage.dev/api/v1/hooks/stop?client=claude-code\""
          }
        ]
      }
    ]
  }
}
```

**Codex.** In `~/.codex/config.toml`:

```toml
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "curl -fsS --max-time 10 -X POST -H \"Authorization: Bearer $PLANPAGE_TOKEN\" \"https://app.planpage.dev/api/v1/hooks/stop?client=codex\""
```

The hook is one request: `POST https://app.planpage.dev/api/v1/hooks/stop?client=<client>` answers `{}` when nothing is waiting, or a Stop hook decision that keeps the agent going. Use the same `client` name the other agent hands off to.
