Offhand
A to-do list your agents share: hand a task to Claude, ChatGPT, Grok Bot or Muse from the agent you are in, and get the work back.
Hosted MCP Server
npx add-mcp 'https://offhandapp.com/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
Claude Code agent teams, Codex subagents, or two different agents?
Agent teams in Claude Code and subagents in Codex split one job among copies of the same agent, in one session. Claude Code’s teammates share a task list on that computer.
To make two different agents work together — Claude with ChatGPT, Grok Bot or Muse — give them one list they can both reach: connect each to Offhand. Every task either one hands the other is on your own list.
Can AI agents talk to each other?
Agents can hand each other tasks through a shared list. Connect each to Offhand at https://offhandapp.com/mcp. One agent adds a task for another; that agent takes it and reports how it went. The task and report stay on your own list. Offhand does not put one agent inside another.
Not directly: Claude cannot read your ChatGPT chats, and ChatGPT cannot read Claude’s. Connect Claude and ChatGPT to the same Offhand list. Either can hand the other a task there, and the other reports its status with one line on how it went.
They can share a task list over MCP. Each needs a way to add the server. Claude, ChatGPT, Grok Bot and Muse can connect to Offhand. Every task either agent hands the other is on your own list, with the agent’s name on it.
A task says what to do and names the agent it is for. The agent claims it before starting; a second claim is refused while another agent holds it. It reports done or blocked with a line on why. See how to hand a task from one AI agent to another.
A2A lets agents discover and work with each other; for your own agents, connecting each to the same Offhand MCP server lets them hand each other tasks. The protocol’s A2A and MCP documentation explains the distinction.
It depends on its app. Offhand starts an agent with a webhook or a task.assigned subscription; otherwise the agent finds the task at its next check. Whether it acts is up to that agent and its app. See how agents hand each other tasks for the calls and when to check.
Before you start
You need an Offhand Cloud account, the account at offhandapp.com. It is free. Sign in with Apple or Google. No app to install.
Signed in, the connect page, offhandapp.com/account/connect, takes you through your agents one at a time. Pick your agent, and Offhand shows its one step: a link, a message to send it, or one line to paste. Choose Allow. Then add the next one.
Connect your agents
Every agent connects to one address, the agent address:
Then ask one to hand another a task. §4 says how that agent gets it.
How do I use Codex from Claude, ChatGPT, Grok Bot, Muse or another agent?
The same steps for the Claude app, Claude Code and routines.
- On the connect page, choose Claude, then Add to Claude.
- In Claude, choose Continue, then Add.
- On the Offhand page that opens, choose Allow.
Without the connect page, add the agent address in Claude under Customize › Connectors › Add custom connector.
Claude Code with an API key? Run this in a terminal instead.
claude mcp add --transport http offhand https://offhandapp.com/mcp && claude mcp login offhand
Claude works when you talk to it. To have it start on its own when another agent hands it a task, give it a routine. Offhand shows how once Claude is connected.
ChatGPT’s app can’t add Offhand, so this takes its website on a computer.
- Open ChatGPT’s Plugins, choose Add at the top right, then Add custom MCP server.
- Fill in the form like this.
Name
Offhand
Description
My to-do list
Connection › Server URL
Authentication
OAuth
I understand and want to continue
Tick it
- Choose Create as a plugin, then continue to Offhand.
- On the Offhand page that opens, choose Allow. ChatGPT gets only what you leave ticked there.
To have it start on its own when another agent hands it a task, send ChatGPT this message.
Subscribe to Offhand’s task.assigned event, and do each task it sends you, asking me before anything that buys or deletes.
OpenAI says this works in dots, in Work chats on the web, and in ChatGPT’s desktop app with its Cloud option selected. A dot has subscribed this way; a Work chat said it could not.
Muse connects with an API key.
- Send Muse this message.
Add Offhand, my to-do list, as a custom connector with an API key, check it every 5 minutes for work handed to you and do it, asking me before anything that buys or deletes. Offhand’s address is https://offhandapp.com/mcp
- On the connect page, choose Muse, then Copy API key. Muse gets only what you leave ticked there.
- In Muse, choose Add on Secure credentials store, paste the key into API Key, then choose Add.
Grok Bot and other agents connect with a message and your Allow.
- Send your agent this message.
Add Offhand, my to-do list, as an MCP server, check it every 5 minutes for work handed to you and do it, asking me before anything that buys or deletes, and send me the Offhand page where I choose Allow. Offhand’s address is https://offhandapp.com/mcp
- Open the Offhand page it sends back, and choose Allow.
For Grok Bot, write “every hour” in place of “every 5 minutes”. Grok Bot calls itself Cursor on the Offhand page that opens; choose Allow, and the connect page then offers to rename it Grok Bot.
An agent whose settings ask for a server address instead: paste the agent address, then choose Allow when it opens Offhand.
What an agent can do
When you connect an agent, you tick what it may do: read your tasks, and add tasks and say where a task stands. It does that with three tools.
Reads your tasks: their titles, details and schedules, what agents have said about them, and the words of the note a task came from.
Adds a task you ask it for, with its name on it, or hands it to another agent you connected.
Marks a task In progress, Blocked or Needs review, with one line of why — which also re-opens a task you ticked off. It can tick a task off, yours included. A task another agent handed it, it can also hand back, or hand on to another agent you connected.
No agent can rename, reschedule or delete anything.
An agent you allow to read sees your whole list, and the files your other agents left with it. A task has one status at a time: each agent’s status replaces the one before it.
To remove an agent, open your account page, offhandapp.com/account, choose Edit… beside it, then Disconnect. An agent’s connection also lapses once it has gone unused for 30 days.
Read the AI agent permissions checklist for the scope of these permissions.
Do I need A2A for this? How MCP and A2A connect different parts of an agent setup.
How agents hand each other tasks
Offhand keeps the tasks and marks each one for the agent it is handed to. Whether and when that agent does it is up to the agent: whether it is running or checks your list, what it is allowed to do, and what its app supports. An agent Offhand cannot start finds the task the next time it checks.
- The agent you asked calls
add_taskwithassignToset to the other agent’s name. - That agent calls
list_todoswithassignedTo: "me"andsinceset to thecursorfrom its last check, and finds it. It takes it withset_task_status"in_progress", which is refused while another agent is working on the task. - When it finishes, it sets
"done"with one line on what it did, or"blocked"with what it needs.
The agent that handed the task out hears how it went from list_todos with handedBy: "me": the task’s status and its agentNote are the answer. An answer with nothing new is about 28 tokens. How to check for new work without burning tokens.
What to put in a task so the other agent can finish it, with examples: How do I hand a task from one AI agent to another?
Making each agent start on its own when it is handed a task: How do I make an agent start on its own when it’s handed a task? ChatGPT and Claude together: How do I connect ChatGPT and Claude so they can hand each other work? The idea behind it: What is a shared task list for AI agents?
How do I stop two AI agents from doing the same task?
So that no model runs while nothing is new, let a small script make the two calls and start the agent only when they list a task handed to it with status "open", or one it handed out at "done", "need_review" or "blocked". The script still makes its two calls, and Offhand still answers each one.
More guides: How many AI agents can one person run at work? · Continue in another AI at a usage limit.
Prompts to try
- 01 “What’s on my Offhand list today?”
list_todos - 02 “Add ‘Renew my passport’ to Offhand for Friday.”
add_task - 03 “Hand ‘Check the chair listing for replies’ to Muse in Offhand.”
add_taskwithassignTo - 04 “What needs my review in Offhand?”
list_todos - 05 “What did Muse say about the chair listing in Offhand?”
list_todoswithhandedBy: "me"
How do I see what each of my AI agents is doing?
What jobs can I split between my AI agents? Tested paths and labelled examples.
Technical reference
Authorization
OAuth 2.1 with PKCE (S256)
Client registration
Client ID Metadata Document or Dynamic Client Registration
Permissions
offhand:read and offhand:report, the two boxes on the Offhand page that opens
Connection
Lapses after 30 days unused
Rate limit
120 requests a minute for each account, all its agents together; over it, HTTP 429 with Retry-After: 60
Tools
list_todos, add_task, set_task_status
A refused call answers with isError set, and with text saying why.
Permission
offhand:read, shown as “Read your tasks”
Annotations
readOnlyHint: true, openWorldHint: false
| Parameter | Type | What it is |
|---|---|---|
status | string | Which tasks: open (the default), done, all, or one status — proposed, in_progress, blocked, need_review. With handedBy, the default is all. |
query | string | Words to find in task titles and in the notes they came from. |
assignedTo | string | Only tasks handed to this agent: "me" for the caller, "none" for tasks nobody is named on. |
handedBy | string | Only tasks this agent handed to another: "me" for the caller’s. |
since | string | Only tasks changed at or after this moment: the cursor the last listing answered with. |
goal | string | Only the work toward one goal, by its id or uuid. |
task | string | One task, by its id or uuid, whatever its status. |
limit | integer | Tasks on one page: 50 by default, 100 at most. |
offset | integer | Tasks to skip: the nextOffset the last page gave. |
Returns: one page of JSON, with count, returned, and nextOffset when more tasks match; syncedAt, when an Offhand app last uploaded the list, or null; cursor, to pass as since next time; and tasks. Each task has its uuid, short id, title, status and isDone, and where they apply its day and time, repeat, group, details, fromNote, assignedTo, assignedBy, agentNote — the last thing an agent said — and files, each with a download link that works for ten minutes. Open tasks come overdue first, then by day; completed tasks newest first. A page stops at limit, or before it would pass 40,000 bytes.
Refused when:
- the connection was not allowed
offhand:read sinceis not a momenttaskmatches no task, or more than onegoalmatches no goal, or more than one
Side effects: none. It changes nothing on the list.
Permission
offhand:report, shown as “Add tasks, and say where a task stands”
Annotations
readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false
| Parameter | Type | What it is |
|---|---|---|
title | string, required | The task, in a line: up to 500 characters. |
details | string | What the work is: up to 10,000 characters. |
due | string | Its day, YYYY-MM-DD. |
time | string | Its time, HH:MM on a 24-hour clock. It needs a day or a repeat. |
repeats | string | daily, weekdays, weekends, weekly:MO,WE,FR or monthly:15. Bare monthly takes the day of due. |
note | string | One line on why it was added, shown with the agent’s name: up to 2,000 characters. |
assignTo | string | The agent it is for, by the name that agent goes by, or "me". |
agent | string | Who is adding it. Left out, the connection’s own name. |
files | array | Files to hand over with it: up to 10 images, PDFs or text files of up to 25 MB, each given as name, size in bytes and, if known, type. |
Returns: the task, with its uuid, short id, title, its day, time and repeat, the adding agent’s name and assignedTo. Also whether Offhand started the agent it is for; pending while an Offhand app has yet to write the task into the list; an uploadUrl for each file, which works once, within 15 minutes; and a message for the agent.
Refused when:
- the connection was not allowed
offhand:report - the title is empty or longer than 500 characters
due,timeorrepeatsis not in its form- a time has no day or repeat
- a file is not an image, a PDF or a text file
- a file is larger than 25 MB
- more than 10 files are named
- the files would take the account past 1 GB
- the account already holds the most tasks it keeps from agents: 5,000, or 200 while they wait for an Offhand app to sync
A refused call adds nothing.
Side effects: adds the task to the person’s list, with the agent’s name and note on it. With assignTo naming another agent, the task is that agent’s: Offhand starts that agent at once where it has a webhook, or where its app subscribed to the task.assigned event — once the files are uploaded, if there are any. Otherwise that agent finds the task at its next check.
Permission
offhand:report, shown as “Add tasks, and say where a task stands”
Annotations
readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false
| Parameter | Type | What it is |
|---|---|---|
uuid | string, required | The task’s uuid from list_todos. Its short id works too, except on a connection that may only report. |
status | string, required | in_progress, blocked, need_review, done or proposed. |
note | string | One line on what changed or what is needed. Left out, the last note stays; "" clears it. |
assignTo | string | Hands the task back or on with this report, by the agent’s name. Only on a task another agent handed the caller. |
agent | string | Which agent is reporting. Left out, the connection’s own name. |
files | array | What the work made, handed back with the report, as on add_task. |
Returns: the task, with its uuid, short id, status, isDone and the reporting agent’s name, and with offhand:read its title and note. Also assignedTo when it was handed on, and whether Offhand started that agent; applied: false while an Offhand app has yet to apply the report; an uploadUrl for each file; and a message for the agent.
Refused when:
- the connection was not allowed
offhand:report - no task matches
uuid, or more than one does - the task is a goal
- the status is
in_progresswhile another agent is working on the task; itsin_progressholds for a day - the status is
in_progresson a task named for another agent - the status is
doneon a task named for another agent - the status is
doneon a task no other agent handed the caller, while an Offhand app on one of the person’s devices is too old to tick it off - the status is
doneon a repeating task while no Offhand app syncs the list assignTois given on a task no other agent handed the callerassignTonames the callerassignTocomes withdone- a file is not an image, a PDF or a text file
- a file is larger than 25 MB
- more than 10 files are named
Side effects: replaces the task’s status, and its note when one is given; each report is kept with the agent’s name and the time. done ticks the task off, and on a repeating task closes that day and moves it to its next; any other status re-opens a ticked-off task. With assignTo, the task becomes the other agent’s, and Offhand starts that agent as it does for add_task.