Neal Desai Staff PM at Scale AI · ex-CTO · evals & agent tooling GH LI

Claude Code plugin · Open source · v0.5, in progress · formerly Todoist Agent

Relay: Delegate to Claude Code From Your To-Do List

A Claude Code plugin that turns the to-do list you already keep into a delegation queue. Give a task the @claude label, from the Todoist app, your phone, or another agent. Run the queue from a terminal and one worker per task does the research or the code on your machine, on your own Claude plan. Each result comes back as a comment on the task with the label switched to you. Workers can read everything you have connected and write only files and branches. Nothing gets sent, deleted, completed, or pushed. No server, no API key, nothing to host.

relay on GitHub Try the guard Install

TL;DR

Label, run, review. The label always says whose turn it is, and a send-back is a comment plus a relabel.

01The queue you already keep

Every to-do list has items that only need a person because, until recently, only a person could do them. Draft the follow-up from a call. Compare three options and recommend one. Fix the flaky test. Prep a brief for Thursday. Each of these ends in a file or a branch that someone reviews, and the reviewing is the part that actually needs you.

Claude Code can do that kind of work, and delegating to it had a shape problem. You had to be at a terminal, compose the prompt right then, wait, and carry the result somewhere else afterward. Meanwhile the tasks themselves already lived in Todoist, next to the dentist appointment. What I wanted was a way to say, from wherever I happened to be, "this one is Claude's," and to have the answer show up where the task was.

That is the whole design. Todoist is the portal. Claude Code, on my machine and my own plan, is the worker pool. A label is the handoff, a comment is the return, and the same label tells me at a glance whose turn every task is on.

02Whose turn is it

add the label app · phone · another agent @claude queued run claims it @claude-working a worker has it result posted @neal your review you complete it send it back: a comment with feedback, then the label goes to @claude and round two builds on round one
The protocol is three labels. A run claims every queued task in one update, so two runs never pick up the same task.

Three labels carry the whole protocol. @claude means the task is queued. @claude-working means a run has claimed it. Your own label, @neal in my case, means the result is back and waiting for you. A run claims every queued task before any work starts, in a single update, so two runs can never pick up the same task, and a run that dies leaves a visible trace that the next run offers to requeue. When a worker finishes, the dispatcher posts one comment and swaps the label to yours.

Review has three outcomes and you choose each one. Keep it, and the task stays yours with its files where they are. Send it back, by leaving a comment with feedback and switching the label to @claude, and the next run hands the worker the full comment history so round two builds on round one rather than starting over. Complete it, and only then does the task close. Claude never completes a task, on purpose. Done is your word.

Because the state lives in Todoist labels rather than a database, anything with Todoist access can take part. The phone app can delegate. Claude on claude.ai with the Todoist connector can delegate, and the repo ships the instruction block to paste into any such agent. The Todoist app is also a perfectly good review surface, since a result is just a comment on the task.

03The brief

A worker starts with no memory of the conversation that produced the task, and nobody answers questions while it runs. So the task has to be the whole brief. The delegate skill writes it in a fixed shape.

Follow up on the Acme kickoff call@claude

Goal: Draft the follow-up to Acme after today's kickoff call.

Done when: An email draft to their team, plus a list of action items with owners and dates.

Folder: ~/Projects/acme-onboarding

Context: Granola notes from "Acme kickoff" today. Pricing questions came up in #acme-deal on Slack.

Stop before: Draft only, don't send.

Acme onboarding · today

Goal in a sentence or two. Done when is the concrete deliverable, a one-page comparison in markdown or a branch with the fix and passing tests, and it is the line the worker grades itself against. Folder is where the work happens and where its files land. Context holds the meeting names, channels, paths, and people that save the worker from guessing, and since the worker can search Slack, notes, docs, and email on its own, it points rather than pastes. Optionally a stop-before line for limits this task carries on top of the standing ones, and a model line when Opus at high effort is more than the task needs.

The skill fills in the folder itself, by matching the task to an existing project directory under your roots or proposing a new one in the same naming style, and asks one round of questions when the goal or the done-criterion is vague. It refuses to create a vague task. Tasks you type on your phone skip all of this, and that is fine. The worker scopes whatever it gets, and when it still cannot tell what done means after looking around, it hands the task back with specific questions instead of guessing.

04The run

Todoist the portal @claude @claude-working @neal tasks · labels · comments app · phone · other agents Dispatcher /relay:run claims · packets · posts · relabels read + claim comment + label task packet RESULT block workeracme-onboarding/ workerinfra-research/ workerweb-app · worktree one subagent per task · up to 4 at once files land in the folder · code commits on a branch · never pushed guard PreToolUse hook reads pass Slack · Granola · Gmail · Drive · web get · list · search · read · query actions denied send · share · delete · complete · push Todoist tools · unknown names · bypass mode too
Todoist is written by the dispatcher only. Workers read the world and write the folder.

/relay:run is the dispatcher, and it is deliberately boring. It reads the queue, claims it, works out each task's folder, builds a task packet, starts one worker subagent per task, and writes each result back. Its only Todoist writes are label swaps and one comment per task. It never edits a title or a description, never reschedules, never completes.

Workers are where the work happens. Each one gets a packet: the task, its comment history oldest first, an absolute work folder, a worktree path if that folder is a git repository, and two standing lines about who you are and where your context lives. Claude Code cannot start a subagent in a different working directory, so the dispatcher resolves every path up front and the worker prefixes its shell commands with a cd. The worker then reads the folder's CLAUDE.md, which is how a project tells workers its sources, its house style, where drafts go, and how the tests run. Up to four workers run at once, each on its own model when the task asks for one.

TASK PACKET
Task ID: 8123
Title: Fix the flaky checkout test
Project: Web app        Priority: p2   Due: none
Work folder: /Users/neal/Projects/web-app
Git worktree: /Users/neal/relay/8123-fix-flaky-checkout/worktree
              on branch claude/8123-fix-flaky-checkout

Description:
the task's description, verbatim

Comment history, oldest first:
earlier results from Claude and replies from the human, or "none"

About the owner: context.about
Where to look for context: context.sources

Code follows one extra rule. The worker never touches your checkout. It creates a worktree under ~/relay on a claude/ branch named after the task, works and commits there, runs the project's tests, and stops. Merging is yours, and so is pushing.

05What comes back

Every worker ends with a RESULT block with a fixed set of fields, and the dispatcher turns it into the comment you see in Todoist.

🤖Claude✅ Ready for review9:42 AM

Scope: Follow-up email plus action items, from the kickoff transcript and #acme-deal. Stopped before sending.

Drafted the follow-up from the Granola transcript and the pricing thread. Six action items, four theirs and two ours, with owners and dates from what was said on the call. The pricing question from Slack is answered in the draft using the tiered numbers from the deck.

Artifacts Your next steps

2026-09-25 · folder: ~/Projects/acme-onboarding

✅ Ready for review
The deliverable meets done-when. Open the files it links to.
🟡 Partly done
Real progress with a stated remainder under your next steps.
❓ Needs your input
Stopped on something only you know. Questions come with options.

Scope in one line says what the worker took the task to mean and where it stopped, which is the first thing to check when a result surprises you. A question, when there is one, arrives in a form you can answer in a comment from your phone, "under $300, or up to $500?" rather than "what do you want?". The review skill walks the queue with questions first, since those are blocked on you, opens each artifact or shows the branch's diff, and asks what to do. The Todoist app does the same job when you are away from a terminal.

06The guard

The line that matters most is this one. Workers can read anything you have connected and cannot act on the world. They gather from Slack, meeting notes, email, Drive, calendars, and the web, and they produce files and branches. They never send, reply, post, share, invite, schedule, delete, archive, complete, or push. A message that needs sending becomes a file with the message in it, listed under your next steps.

A prompt says that. A hook enforces it. The guard runs before every tool call and first checks the agent type on the event, so it acts only on calls from the worker subagent and leaves your own session alone. For MCP tools it splits the tool name into words, camelCase and underscores both, and classifies by verb. A hard verb like send, share, delete, or complete is denied no matter what. A read verb like get, search, list, or query passes. Anything else, including create, update, and draft, is denied unless you allow-listed that exact tool in the config, and a hard verb wins even over the allow-list. A name it cannot classify at all is denied, because failing closed is the only sensible default for a rule written against connectors you have not seen yet. Every Todoist tool is denied to workers outright, since the dispatcher owns Todoist. Shell commands get a deny list: git push, the destructive git verbs, gh and curl writes, package publishing, ssh and sudo, and rm aimed at a top-level folder. Edits under ~/.claude and the plugin's own config are refused. Denials hold in bypass-permissions mode, which is what makes an unattended run safe to walk away from.

The whole thing is 160 lines of Python with 16 tests. The console below runs the same rules, ported line for line, so you can try your own tool names and shell commands against it.

› mcp
PASS

07Why this shape

The same shape carries beyond Todoist. Any system with a label, a comment thread, and an API that another agent can reach makes a delegation portal, and the split holds: one boring dispatcher that owns the portal, workers that own a folder, and a guard between the workers and the world.

08Install

You need a Todoist account and Claude Code signed in with a Claude plan. Connect Todoist's official connector, add the plugin from its marketplace, and run setup, which creates the three labels and asks for your label and your projects folder.

# connect Todoist to Claude Code, then /mcp → todoist → Authenticate
claude mcp add --transport http --scope user todoist https://ai.todoist.net/mcp

# inside Claude Code
/plugin marketplace add nbdesai1992/relay
/plugin install relay@relay
/relay:setup

# then
/relay:delegate Draft the follow-up from today's Acme kickoff call
/relay:run
/relay:review

Relay is open source under MIT at github.com/nbdesai1992/relay. Companion pieces: Overtone, another small utility built around Claude Code, and Escalation Bench, which measures the ask-versus-act judgment that the worker's needs-input status depends on.