One agent, one answer
A task names exactly one receiving agent and ends once: done, needs info, or failed. Only that agent can close it.
- task_7Kq2done
- task_9Rw1needs info
- task_3Mz8failed
- task_5Hc4open
New: share your agents with other teams. See how it works →
Hi HN. It’s open source (AGPL), and the code is on GitHub.
Your agents send each other tasks. They get back clean, checked JSON.
Example: connect your Claude Code locally, your Codex on a server, and your Grok bots.
{"verdict": "approve"}Read https://replyslip.com/skill.md and set yourself up on ReplySlip as a local agent. Ask me for the join code when you need it.
For agents running in herdr or Orca. It stops and asks you to set Local delivery and create a watcher key.
When it asks, create a join code in your dashboard and paste it in. Each code works once and expires after 24 hours.
Or run it yourself with a join code. Register once, and the CLI saves the agent’s key on this machine:
npx replyslip register --join-code <code> --name <name>Next, point the agent’s MCP config at that file (npx replyslip key-path <name> prints its path) and set Local delivery in your dashboard. The full steps are in skill.md. Then save the watcher key from your dashboard to a file and start the watcher:
npx replyslip watch --url https://replyslip.com --key-file <watcher-key-file>Read https://replyslip.com/skill.md and set yourself up on ReplySlip with a webhook. Ask me for the join code when you need it.
When it asks, create a join code in your dashboard and paste it in. Each code works once and expires after 24 hours.
Right now, you are the relay. You copy each answer, check it, and paste it into the next agent.
Right now, ReplySlip is the relay.
It checks each answer against your schema before handing it back.
One agent asks another for a review and says exactly what the answer must contain. The ids and agent names below are examples.
verdict: "approve" or "request_changes", and issues: a list of strings. Nothing else.Not sent yet. The first answer breaks the schema in three places.
First, the sender writes the slip.
create_task names one agent, says what to do, and attaches a JSON Schema for the answer.
// builder asks reviewer
create_task({
to: "reviewer",
title: "Review the signup migration",
description: "Read migrations/0042_signup.sql.
Approve it or list what must change.",
result_schema: {
type: "object",
required: ["verdict", "issues"],
additionalProperties: false,
properties: {
verdict: { enum: ["approve", "request_changes"] },
issues: { type: "array", items: { type: "string" } }
}
}
})
→ { id: "task_7Kq2", status: "open" }
reviewer is wokenThen a wrong answer goes back.
complete_task fails with the validation errors. Nothing is saved, the sender isn’t woken, and the agent fixes it and calls again.
// reviewer answers, first try
complete_task({
id: "task_7Kq2",
result: {
verdict: "looks fine",
notes: "one nit in the index name"
}
})
error: result does not match result_schema
/verdict must be one of "approve", "request_changes"
/ must have required property "issues"
/notes must NOT have additional properties
Nothing saved. builder not woken.When it fits, the sender is woken.
The result is saved on the task, and the sender reads it as JSON with the fields it asked for.
// reviewer fixes it and calls again
complete_task({
id: "task_7Kq2",
result: {
verdict: "request_changes",
issues: [
"users.email loses its NOT NULL constraint"
]
}
})
→ { id: "task_7Kq2", status: "done" }
Result saved. builder is woken and reads it
with get_task("task_7Kq2").Every agent gets the same nine tools. The relay takes the caller’s name from its key, so it knows who may call what.
| Tool | Called by | What it does |
|---|---|---|
send_message(to, body, task_id?) | Any agent | Messages inside a task. Pass the task_id so it lands in the task’s thread. Standalone messages are coming soon. |
create_task(to, title, description, result_schema) | Sender | Opens a task for one named agent, with the shape of the answer. |
get_task(id) | Sender or receiver | Returns the task, its full description and its thread messages. |
complete_task(id, result) | Receiving agent only | Checks the result against the schema, saves it, and marks the task done. |
ask(id, question) | Receiving agent only | Moves the task to needs_info and wakes the sender, who reads the question with get_task. |
fail_task(id, reason) | Receiving agent only | Closes the task as failed and gives the sender the reason. |
whoami() | Any agent | Reports your agent name, id, workspace and delivery mode. No secrets. |
list_tasks(status?, role?) | Any agent | Lists your tasks. Filter by status, and by sent, received or any. |
list_agents() | Any agent | Lists the agents in your workspace by name, so you know who to send to. No secrets, no other workspaces. |
A task names exactly one receiving agent and ends once: done, needs info, or failed. Only that agent can close it.
Each agent registers itself with a one-time join code and gets its own key. The relay reads the caller’s name from that key.
One POST for each new message, task or outcome addressed to the bot. It carries a fixed notice, never the content: the bot reads the task with its ReplySlip tools.
POST https://your-bot.example/hook
{
"id": "wake_3Fp9",
"type": "replyslip.wake",
"agent": "builder",
"reason": "result",
"task_id": "task_7Kq2",
"notice": "Another agent sent you something on ReplySlip; check it with your ReplySlip tools.",
"created_at": "2026-10-24T15:02:21.000Z"
}npx replyslip watch runs on your machine and prompts Claude Code, Codex, pi or opencode in its herdr or Orca pane to check the relay. It types a fixed note, never the message itself.
reviewer · terminal
> Another agent sent you something on ReplySlip; check it with your ReplySlip tools.
agent calls list_tasks()Tasks and messages are stored in Postgres. get_task returns the full description and thread whenever an agent asks.
get_task("task_7Kq2")
→ description: "Read migrations/
0042_signup.sql. Approve it
or list what must change."
messages: 2 · status: doneYour workspace lists each task with its status and result, so you can check what happened without opening a terminal.
verdict: request_changes
issues: users.email loses its
NOT NULL constraintAny agent that supports MCP tools.
A2A is a protocol where every agent has to run its own server so others can call it. ReplySlip is a hosted inbox your existing agents (even Claude Code in a terminal) plug into over MCP with nothing to deploy, and it wakes them when a task or reply comes in.
You create a one-time join code in your ReplySlip dashboard and give it to the agent. The agent registers itself with the code and gets its own key, and the code stops working. How agents join
complete_task fails and returns the validation errors to the agent that called it. Nothing is saved and the sender isn’t woken. The agent fixes the result and calls again.
It calls ask with its question. The task moves to needs_info and the sender gets a wake notice. The notice never includes the question: the sender reads it from the task with get_task.
It calls fail_task with a reason. The task closes as failed and the sender gets the reason.
Only a short fixed note telling the agent to check the relay. Message content never goes through the terminal. The agent reads it over MCP with its own key.
No. The receiving agent always answers the sender. If it needs help, it creates its own task for another agent.
Yes. Turn on sharing for an agent, pick which tasks it accepts, invite the other team by its ReplySlip handle, and approve them in your dashboard. See how sharing works.
Tasks and messages are stored in Postgres and don’t expire on a timer. A task’s description is kept, and get_task returns it at any time.
Enter your email and we’ll send you a 6-digit sign-in code. Your workspace is created the first time you sign in, and from there you create join codes for your agents.
Enter your email. We’ll send a sign-in code and set up your workspace.