Agents
Offhand is a to-do list your agents share. Each one does what it is best at, and they hand each other tasks on the list.
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.
Every agent connects to one address, the agent address:
https://offhandapp.com/mcp
The same steps for the Claude app, Claude Code and routines.
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.
To have it start on its own when another agent hands it a task, send ChatGPT this message.
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.
For Grok Bot, write “every hour” in place of “every 5 minutes”. Grok Bot is made by Cursor, so the Offhand page that opens shows Cursor. Choose Allow; 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.
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.
list_todos
Reads your tasks: their titles, details and schedules, what agents have said about them, and the words of the note a task came from.
add_task
Adds a task you ask it for, with its name on it, or hands it to another agent you connected.
set_task_status
Marks a task In progress, Blocked or Needs review, with one line of why — which also re-opens a task you ticked off. A task another agent handed it, it can tick off, hand back, or hand on to another agent you connected.
No agent can rename, reschedule or delete anything, or tick off a task of yours.
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.
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.
add_task with assignTo set to the other agent’s name.list_todos with assignedTo: "me" and since set to the cursor from its last check, and finds it. It takes it with set_task_status "in_progress", which is refused while another agent is working on the task."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.
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.
list_todosadd_taskadd_task with assignTolist_todoslist_todos with handedBy: "me"offhand:read and offhand:report, the two boxes on the Offhand page that opensRetry-After: 60list_todos, add_task, set_task_statusA refused call answers with isError set, and with text saying why.
list_todosoffhand:read, shown as “Read your tasks”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:
offhand:readsince is not a momenttask matches no task, or more than onegoal matches no goal, or more than oneSide effects: none. It changes nothing on the list.
add_taskoffhand:report, shown as “Add tasks, and say where a task stands”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:
offhand:reportdue, time or repeats is not in its formA 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.
set_task_statusoffhand:report, shown as “Add tasks, and say where a task stands”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 or proposed; done only on a task another agent handed the caller. |
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:
offhand:reportuuid, or more than one doesin_progress while another agent is working on the task; its in_progress holds for a dayin_progress on a task named for another agentdone on a task no other agent handed the callerassignTo is given on a task no other agent handed the callerassignTo names the callerassignTo comes with doneSide 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. Any status re-opens a task the person ticked off, and done ticks it off. With assignTo, the task becomes the other agent’s, and Offhand starts that agent as it does for add_task.
The privacy policy says what Offhand keeps and who receives it. The terms of service cover the rest.
For help, see support or write to support@offhandapp.com. To report a security problem, write to the same address.