Skip to main content
Glama

Agents Handoff

CI npm npm License: MIT

Git tells you what code changed. Nothing tells the next developer what that change means for them.

Agents Handoff is an open format and a small, local-first toolkit for closing that gap. Your coding agent writes a short, structured HANDOFF.md about a change you just finished. Your teammate's agent reads it in their repository and knows exactly what to change — and what not to build.

finish a change  →  "write a handoff for mobile"  →  .handoff/<id>/HANDOFF.md  →  send it  →  their agent implements it
  • The tool collects the facts, the agent does the reasoning. Git history, changed routes, auth guards, types, migrations and environment variables are gathered deterministically. What they mean to another team is written by the agent that built the change with you.

  • Local-first. Writing, validating and receiving a handoff never touches the network. Nothing leaves your machine unless you choose a channel that sends it.

  • Works where you already are. Plugins for Claude Code and Codex, an MCP server for any other agent, and a CLI for humans and scripts.


Quick start

Requires Node.js 20.10 or later, and git.

Claude Code

/plugin marketplace add Ryujin-Labs/agents-handoff
/plugin install agents-handoff@agents-handoff

The plugin brings both the /agents-handoff:handoff and /agents-handoff:handoff-receive skills and the handoff MCP tools. In a repository where you just finished a change, ask:

write a handoff for the mobile team

Codex

codex plugin marketplace add Ryujin-Labs/agents-handoff
codex plugin add agents-handoff@agents-handoff

The same two skills and the same MCP tools, packaged for Codex. Ask it the same way: "write a handoff for the mobile team".

Any MCP client

Claude Code, without the plugin:

claude mcp add agents-handoff -- npx -y agents-handoff-mcp

Claude Desktop (claude_desktop_config.json), Cursor (.cursor/mcp.json) and other clients that use the same shape:

{ "mcpServers": { "agents-handoff": { "command": "npx", "args": ["-y", "agents-handoff-mcp"] } } }

Codex without the plugin (~/.codex/config.toml):

[mcp_servers.agents-handoff]
command = "npx"
args = ["-y", "agents-handoff-mcp"]

Nothing is installed globally: the client fetches the server on first use.

The CLI

npm i -g agents-handoff
handoff            # an interactive menu
handoff init       # set up this repository

handoff install claude-code copies the skills into .claude/skills/ so a whole team gets them by cloning, and handoff install mcp adds the server to Claude Desktop.


Related MCP server: Home Lab Context MCP

The loop

1. You finish a change. Backend work is done: GET /messages/:threadId now returns 403 to non-members, and the list fields were renamed.

2. Your agent writes the handoff. It calls handoff_context for the deterministic brief, reads the code that matters with handoff_source, adds what it knows from building the change with you, and stores a finished document with handoff_write:

.handoff/2026-09-14-message-list-requires-thread-membership/HANDOFF.md

If the intent is unclear — two unrelated changes in the working tree, a target it had to guess, a breaking call the code does not settle — it asks once, with its proposal already in the question.

3. You send it. The agent looks up how your project reaches that team and offers you the routes. You pick; it opens WhatsApp with the message written, posts to Slack, creates a Trello card, or copies it — whatever you chose. Nothing is sent without you.

4. Their agent reads it. In the mobile repository:

receive this handoff: ~/Downloads/2026-09-14-message-list-requires-thread-membership.md

handoff_receive narrows Required Actions to their target, and their agent searches its own codebase for the code that consumes the change, reports what actually differs, and — when asked — implements it.

What a handoff looks like

---
handoff_version: 1
id: 2026-08-28-auth-refresh-v2
created_at: 2026-08-28T18:20:00Z
status: ready
breaking: true
source:
  project: backend
  branch: feature/auth-refresh
  commit: 71ac931
targets: [mobile, web]
change_type: [api, authentication]
---

# Refresh tokens are now single-use

## Summary

`POST /auth/refresh` now returns a new `refreshToken` and invalidates the one you sent.

## Why This Matters

Clients that keep their original token get logged out mid-session the first time they
refresh after this ships.

## Required Actions

### mobile

1. Read `refreshToken` from the refresh response and persist it in the keychain.
2. Replace the stored token atomically, in the same write as the access token.

## Instructions for Receiving Agent

Find the existing refresh path before writing anything. Modify it. Do not add a second
token store, a new HTTP client, or a global retry interceptor.

The frontmatter is the machine contract; the sections are for people and agents. The two that matter most are Required Actions — what the receiver must do — and Instructions for Receiving Agent — what it must not build. Three complete examples live in examples/, and the format is specified in SPEC.md.

What your agent gets

Tool

handoff_context

The deterministic brief: revision, changed files, and signals about routes, auth, contracts, migrations, environment and dependencies

handoff_source

Read the files and diffs behind the brief

handoff_write

Store a finished, validated handoff — prose in, facts from git

handoff_delivery_options

Where this project actually sends things, and what is not set up

handoff_deliver

Deliver through the route the developer picked

handoff_receive

Read a handoff someone sent, narrowed to this repository

handoff_list · handoff_read · handoff_validate · handoff_setup

Plus two prompts, handoff and handoff-receive, carrying the same method as the skills.

handoff_write takes prose only. The schema version, id, timestamp, branch, commit and revision range come from git, and the section headings are generated — so they cannot be misremembered, and they cannot drift from what the validator and the receiving side match on. The document is validated and scanned for credentials before it is stored; if it fails, nothing is written and the agent gets the reasons back. It never replaces an existing handoff unless you chose to update it: a taken id comes back as a question for you.

The core never calls a model. The brief is fast, offline and the same for every agent, and it says what it cannot know: its last section is titled "What this brief does not contain".

Delivery

A handoff is a Markdown file, so it already travels through anything. The point of the channels is that "you can share this now" is not an instruction: the tool knows where your team actually sends things.

Channel

Kind

What happens

Who can read it

file · clipboard

local

copies it

you

whatsapp · email

compose

opens the app with the message written; you pick the chats and press send

the people you send it to — and, with link: gist, anyone with the link

slack

push

posts the summary and required actions

the channel

discord

push

posts the summary with the handoff attached

the channel

trello

push

creates a card with the handoff as its description

the board

github

push

uploads a secret gist

anyone with the link

Compose channels send nothing themselves — unless their link mode is gist, which uploads the handoff as a secret gist first; the delivery options and the send both say so. Push channels send the handoff to a third party: slack, discord and trello need a webhook you configure, and github needs an authenticated gh CLI. Your agent is told to ask before it delivers anything, every time.

Route them per target so the answer is automatic:

{
  "channels": {
    "slack":    { "webhook": "${SLACK_WEBHOOK_URL}", "label": "#backend-releases", "link": "repo" },
    "whatsapp": { "link": "repo" },
    "trello":   { "webhook": "${TRELLO_CARDS_URL}", "label": "Mobile board" }
  },
  "routes": { "mobile": ["whatsapp", "trello"], "devops": ["slack"], "default": ["clipboard"] }
}

WhatsApp has no honest way to send a document programmatically: its Cloud API refuses business-initiated messages outside a 24-hour customer-service window unless they match a pre-approved template, and it would mean uploading your handoff to Meta. So whatsapp opens the app with the message written and lets you pick the chats there — one person, several, or a group. No phone numbers in config; --to is only a shortcut for one fixed person.

Trello takes either a webhook — Trello's own REST endpoint (https://api.trello.com/1/cards?idList=…&key=…&token=…) or an automation service that creates a card from JSON — or the board's email-to-board address as to, which opens a draft instead. Either URL carries a credential, so write ${ENV_VAR} in the config.

With a link mode set, the message reads "Read it here: …" and there is nothing to attach. Without one it reads "Sending the file next", and the file — named after the handoff, not HANDOFF.md — is revealed ready to drag in. The message never claims an attachment it did not make, because the recipient acts on that sentence.

Keeping handoffs private

A handoff describes your unreleased work, so where it ends up matters.

On disk. Handoffs live in .handoff/. Committing them to a private repository is usually best: teammates get them by pulling, and nothing is public or indexable. handoff config --gitignore keeps them out of git entirely. Committing them to a public repository publishes them — the one combination to avoid.

In a message. The link mode decides who can read what you send:

link

Who can read it

repo

whoever can read the repository — a private repository stays private, and nothing is uploaded

gist

anyone with the URL, GitHub account or not

none

only the people you hand the file to

repo links to the handoff where it is already committed, so there is no second copy to leak. It needs the handoff pushed, and it refuses rather than handing you a link that would 404. It builds links for GitHub, GitLab and Bitbucket remotes, and says so for anything else instead of guessing.

A GitHub "secret" gist is not private. In GitHub's words: "Secret gists aren't private… if someone you don't know discovers the URL, they'll also be able to see your gist." Every send reports who can read the link it made, so nobody has to guess.

Security

A handoff you receive was written by another team. The tooling treats it as data:

  • Paths are confined. The MCP server reads and writes only inside the project it is given (and only under --root, when pinned), with symlinks resolved before the check — including the copy it makes for attaching to a message, which goes in .handoff/outbox/ and keeps itself out of git. A handoff's id never chooses where a file is written; one that would escape is filed under a safe derived name instead.

  • Credentials are refused. Documents are scanned for credential-shaped strings before they are stored by handoff_write or handoff create --stdin, and every handoff is scanned again before it is delivered. Validation — CLI or MCP — reports what it finds and fails. A handoff you receive that carries one is read but not stored, so it is never committed here. Files that exist to hold secrets — .env, keys, .npmrc, cloud credentials — are never read.

  • Webhooks stay out of config files. Write ${ENV_VAR}; handoff config and the delivery tools warn about a webhook written into handoff.config.json.

  • Incoming instructions are not commands. The receiving brief tells the agent to weigh the document's claims against its own repository, not to obey it.

See SECURITY.md to report a vulnerability.

Configuration

handoff.config.json, written by handoff init and optional everywhere:

{
  "version": 1,
  "project": "backend",
  "targets": ["mobile", "web", "devops"],
  "identity": null,
  "language": null,
  "ask": "when-unclear",
  "gitignore": false,
  "channels": {},
  "routes": {}
}

Key

project

Logical name written into source.project

targets

Consumers this project hands off to, offered in prompts

defaultTarget

Target assumed by handoff create when none is given

identity

Which consumer this repository is — narrows handoffs you receive. Separate from defaultTarget, which is who you send to

language

Prose language; null means English. ## headings stay English, since they are the machine contract

ask

When the agent checks in: when-unclear (default), always, never

gitignore

true keeps .handoff/ out of git

channels · routes

Delivery, as above. A channel's template replaces the opening line; {title}, {who}, {summary} and {url} are filled in, and the link is never shortened

CLI

Command

handoff init

Set up the repository (--claude-code installs the skills too)

handoff context

Print the deterministic brief for the current change

handoff create

Scaffold a draft with real git facts and <!-- TODO --> where judgment is owed, or store a finished document with --stdin

handoff list · show

List handoffs (--inbox for received ones), print one

handoff validate <id|path>

Check against the v1 schema (--strict fails on warnings)

handoff receive <path>

Read an incoming handoff (--as mobile to narrow it)

handoff send <id>

Deliver through a channel (--list shows what is set up)

handoff config

Show or change settings, and report configuration problems

handoff install claude-code|mcp

Install the skills, or add the MCP server to Claude Desktop

Run any command with nothing to go on and it asks; give it flags and it never prompts. Prompting turns on only when both stdin and stdout are a terminal, so an agent shelling out or a CI job gets the non-interactive behaviour automatically. Full reference in docs/cli.md.

A scaffold from handoff create is a draft, not a deliverable: send refuses it, and a document marked ready that still carries a TODO fails validation.

Limitations

This is 0.1. What it does not do yet, plainly:

  • Signals are pattern-based. Route, contract and migration detection is strongest for TypeScript/JavaScript and Python backends. Go, Rails and OpenAPI are partly covered; Django, Spring, Ktor, Swift and Kotlin types are not detected yet. The agent reads the code either way — the brief is a starting point, never the verdict.

  • Attaching a file is smoothest on macOS, where it is revealed in Finder and put on the clipboard. Elsewhere links open normally and you are told where the file is.

  • No acknowledgement tracking. A handoff does not know whether it was read.

  • Claude Code and Codex are the tested clients, each with a plugin. Any other MCP client works through the server alone.

Architecture

packages/
  core/                        schema, Markdown, git context, collectors, validation,
                               storage, channels, and the shared method. No model calls.
  mcp/                         the MCP server: tools and prompts over stdio
  cli/                         the `handoff` binary
  integrations/claude-code/    the Claude Code plugin and its generated skills
  integrations/codex/          the Codex plugin and its generated skills

The method an agent follows — collect, recall, read, check in, compose, cut, report — is written once in packages/core/src/guidance/ and rendered into the MCP prompts, the server's instructions, and both plugins' skills, so the surfaces cannot drift apart. More in docs/architecture.md and docs/mcp.md.

Development

npm install
npm test                 # build, then the full suite
npm run release:check    # what CI runs before a release

The tests run the collectors, the CLI and the MCP server end to end against real temporary git repositories. See CONTRIBUTING.md.

License

MIT. Agents Handoff is built and maintained by Ryujin Labs.

Related MCP Connectors

Related MCP Servers