Offhand

Agents

How your agents use Offhand

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.

Agent address
offhandapp.com/mcp
Account
Offhand Cloud, free
Sign in
Apple or Google
Tools
Three

Before you start

§1

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 an agent

§2

Every agent connects to one address, the agent address:

https://offhandapp.com/mcp

Claude

The same steps for the Claude app, Claude Code and routines.

  1. On the connect page, choose Claude, then Add to Claude.
  2. In Claude, choose Add, then Connect.
  3. 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

  1. Open ChatGPT’s Plugins, choose Add, then Create custom MCP server.
  2. Fill in the form like this.
Choose icon
Offhand.png
Name
Offhand
Description
My to-do list
Connection › Server URL
https://offhandapp.com/mcp
Authentication
OAuth
I understand and want to continue
Tick it
  1. Choose Create as a plugin, then continue to Offhand.
  2. 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 sends, 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, Grok Bot and other agents

  1. 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 sends, buys or deletes, and send me the link to sign in: https://offhandapp.com/mcp

  1. Open the link it sends back, and choose Allow.

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.

What an agent can do

§3

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.

How agents hand each other tasks

§4

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.

  1. The agent you asked calls add_task with assignTo set to the other agent’s name.
  2. That agent calls 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.
  3. 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.

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.

Prompts to try

§5

Technical reference

§6
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.

list_todos

Permission
offhand:read, shown as “Read your tasks”
Annotations
readOnlyHint: true, openWorldHint: false
ParameterTypeWhat it is
statusstringWhich tasks: open (the default), done, all, or one status — proposed, in_progress, blocked, need_review. With handedBy, the default is all.
querystringWords to find in task titles and in the notes they came from.
assignedTostringOnly tasks handed to this agent: "me" for the caller, "none" for tasks nobody is named on.
handedBystringOnly tasks this agent handed to another: "me" for the caller’s.
sincestringOnly tasks changed at or after this moment: the cursor the last listing answered with.
goalstringOnly the work toward one goal, by its id or uuid.
taskstringOne task, by its id or uuid, whatever its status.
limitintegerTasks on one page: 50 by default, 100 at most.
offsetintegerTasks 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
  • since is not a moment
  • task matches no task, or more than one
  • goal matches no goal, or more than one

Side effects: none. It changes nothing on the list.

add_task

Permission
offhand:report, shown as “Add tasks, and say where a task stands”
Annotations
readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false
ParameterTypeWhat it is
titlestring, requiredThe task, in a line: up to 500 characters.
detailsstringWhat the work is: up to 10,000 characters.
duestringIts day, YYYY-MM-DD.
timestringIts time, HH:MM on a 24-hour clock. It needs a day or a repeat.
repeatsstringdaily, weekdays, weekends, weekly:MO,WE,FR or monthly:15. Bare monthly takes the day of due.
notestringOne line on why it was added, shown with the agent’s name: up to 2,000 characters.
assignTostringThe agent it is for, by the name that agent goes by, or "me".
agentstringWho is adding it. Left out, the connection’s own name.
filesarrayFiles 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, time or repeats is 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.

set_task_status

Permission
offhand:report, shown as “Add tasks, and say where a task stands”
Annotations
readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false
ParameterTypeWhat it is
uuidstring, requiredThe task’s uuid from list_todos. Its short id works too, except on a connection that may only report.
statusstring, requiredin_progress, blocked, need_review or proposed; done only on a task another agent handed the caller.
notestringOne line on what changed or what is needed. Left out, the last note stays; "" clears it.
assignTostringHands the task back or on with this report, by the agent’s name. Only on a task another agent handed the caller.
agentstringWhich agent is reporting. Left out, the connection’s own name.
filesarrayWhat 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_progress while another agent is working on the task; its in_progress holds for a day
  • the status is in_progress on a task named for another agent
  • the status is done on a task no other agent handed the caller
  • assignTo is given on a task no other agent handed the caller
  • assignTo names the caller
  • assignTo comes with done
  • 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. 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.

Privacy, terms and security

§7

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.