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.
TL;DR
- What it is. Four skills, one worker agent, and a guard, packaged as a Claude Code plugin. delegate writes the brief, run works the queue, review walks you through what came back, and setup takes about ten minutes.
- Labels are turns. @claude means queued, @claude-working means a run has claimed it, and your own label means it is back with you. The task's comment thread is the memory, so a send-back builds on the previous round instead of starting over.
- The split. The dispatcher is the only thing that touches Todoist, and its only writes are label swaps and one comment per task. Workers own a project folder, or a git worktree on a claude/ branch, and never see Todoist at all.
- The boundary. A PreToolUse hook scopes itself to the worker and makes the outside world read-only. Tool names are classified by their verbs, unknown names fail closed, and send, share, delete, complete, and push stay denied even for allow-listed tools and even in bypass mode.
- Why it matters. Delegation stops requiring a terminal. Capture a task wherever you are, run the queue when you are back, review in the app you already open. Several tasks run at once and each lands as a reviewable artifact.
- Where it is. Version 0.4, in progress and still changing. Manual runs today. Scheduled runs are one line of cron away and come once manual runs have proven themselves.
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
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.
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.
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
/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.
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~/Projects/acme-onboarding/follow-up-email.md, the draft, addressed to their team~/Projects/acme-onboarding/action-items.md
- Read the draft, adjust the tone, and send it from your account.
- Confirm the two dates I inferred for our side.
2026-09-25 · folder: ~/Projects/acme-onboarding
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.
07Why this shape
- No server. Everything runs inside a Claude Code session on your own plan. There is no daemon to keep alive, no API key to rotate, and no third place where your tasks and their results live. The install is a marketplace add and a setup command.
- Labels instead of a database. The queue state is visible in the app you already open, editable with a tap, and legible to any other tool with Todoist access. A state machine you can see is a state machine you can repair by hand.
- Todoist as the portal. Because delegation is a label and review is a comment, the phone works, the watch works, and another agent works. The repo ships a paste-in instruction block so Claude on claude.ai can queue work for Claude Code on your machine.
- A boring dispatcher. Every Todoist write goes through one skill, and that skill only swaps labels and posts one comment per task. Workers do everything else and cannot reach Todoist at all. The blast radius of a confused worker is a folder.
- Done is a human word. Claude can mark a result ready, partial, or blocked. Only you complete a task. It sounds small, and it is the difference between an assistant and something that closes tickets on your behalf.
- Worktrees. A code task never touches your checkout. You review a branch, and merging is an ordinary decision you make with an ordinary tool.
- Cron is one line. The run step is a plain skill, so
claude -p "/relay:run"on a schedule turns it into an overnight batch. That is the next step, once manual runs have proven themselves.
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.