Skip to content

feat: tasks between bots through MCP tools (M3, part 2) - #4

Merged
httpsphl merged 10 commits into
mainfrom
feat/m3-tools
Sep 28, 2026
Merged

httpsphl merged 10 commits into
mainfrom
feat/m3-tools

Conversation

@httpsphl

Copy link
Copy Markdown
Owner

Second and last slice of M3 (spec 17): bots message each other and hand off tasks through MCP tools. This PR is built on #3. Merge #3 first (merge commit); this diff then shrinks to the five commits below.

With this PR, M3's acceptance test passes with real Claude Code: two bots exchange a task and its result without anyone stepping in.

What changes

  • POST /mcp gives each bot four tools: crew_roster, send_message, complete_task and my_tasks. It is authenticated with the bot token of a live generation.
  • Both MCP eras are supported. Claude Code 2.1.284 first sends server/discover (MCP 2026-07-28, no handshake). If that fails, it falls back to initialize (2025-11-25). The server answers both:
    • 2026-07-28 side: header/body checks (-32020), unsupported versions (-32022), 404 for unknown methods, and the caching hints (ttlMs, cacheScope). Without those hints, Claude Code rejects the tool list.
    • initialize side: version negotiation, notifications get 202, and GET/DELETE get 405.
  • Tasks (spec 9.4):
    • Chains: a task continues the chain the sender is working on (its open task with the most hops), and is refused past max_hops with an explanation.
    • Deadlines: from 1 minute to a week.
    • Completion: only the assignee can complete a task, even after the deadline. The result goes back to the requester as a message, in the same transaction.
    • Expiry: the courier expires overdue tasks and tells the requester.
  • Errors the model can fix (unknown handle, bad argument, hop limit, someone else's task) come back as isError results with a sentence it can act on.
  • Crew boundaries: a bot cannot reach bots or tasks of another crew.
  • Generated settings allow mcp__botloft, so a bot on its own is never stuck at a permission prompt. The trust dialog shows this rule. The bot rules now ask bots to close every task with complete_task.
  • tasks.list is available to the app.
  • Debug log has one line per MCP request, with the method name only, never arguments.

Tests

  • tests/mcp.rs:
    • discovery, caching hints and the tool list;
    • the initialize path;
    • 401, 403 and 405 responses;
    • version and header mismatches, including the base64 Mcp-Name form;
    • unknown methods and tools;
    • token revoked after a restart.
  • tests/tasks.rs, bot to bot through the tools with FakeRuntime and FakeInbox:
    • exact envelopes both ways;
    • my_tasks;
    • the hop limit, with origin and hops checked;
    • an overdue task expiring, then completed late;
    • tool errors;
    • crew isolation.
  • Store: listing, overdue tasks, and settling together with the report.
  • Every commit builds and passes clippy and fmt on its own. Full suite: 137 tests, plus pnpm check.

Real Claude Code checks (done, 2.1.284)

  1. MCP handshake, without spending tokens. claude -p --mcp-config ... --model <unknown> connects its MCP servers before failing on the model. A logging server showed server/discover with the 2026-07-28 headers, then initialize with 2025-11-25, and the Authorization header with ${VAR} expanded.
  2. Tool list accepted. In a running bot, /mcp first showed "botloft · tools fetch failed". The cause was the missing caching hints. After the fix it shows "botloft · 4 tools", with a single tools/list per bot.
  3. Acceptance test (dev daemon, bots @probe and @helper in one crew, folders trusted):
    • The owner asked @probe via messages.send to send @helper a task: the sum of 17 and 25.
    • @probe called send_message.
    • @helper got [botloft] from @probe · crew Real Test · task tsk_... · due in 2 h and called complete_task with "17 + 25 = 42".
    • @probe got the result and answered RESULTADO 42.
    • The task shows done with hops: 1, and all 5 deliveries are sent with 0 failed attempts. The daemon log has no message bodies or tokens.

Notes

  • Bot environments load the owner's user-level Claude Code settings, including user hooks and claude.ai connectors. The bots here also saw the owner's connectors. This behavior was not changed in this PR; it may deserve a decision before M5.

The message line of the Claude Code inbox is not documented, so it was
tested with Claude Code 2.1.283 before the courier depends on it: the
line in 9.2 is delivered and opens a turn, a missing or wrong auth line
closes the connection, and Claude Code never replies. The same run
confirmed --continue resuming a real conversation.

Also settles what the courier does: in order and one at a time per bot,
a 10 s send timeout, deterministic retry backoff, archived bots' queues
dropped, and an English envelope with a relative deadline.
Protocol types for spec 9 and 11 (Message, Delivery, Task, their
enums and the messages.*/deliveries.* params), exported to TypeScript,
plus the envelope a bot reads when a message arrives and validation for
message bodies.
Migration 0002 creates the three tables of spec 12. A message is saved
with its delivery and the task it creates in one transaction. Delivery
transitions are single conditional UPDATEs; due_deliveries hands out
only each bot's oldest pending delivery, and only while none of that
bot's deliveries is being sent, so messages arrive in order.
Opens the bot's named pipe as a client, retries for up to 2 s while it
is busy, writes and closes. The address comes from a bot's hook, so
only plain names under \\.\pipe\ are accepted: a ".." segment could
make CreateFile open a real file.
The courier moves pending deliveries into the bots' inboxes: it waits
without counting attempts while a bot is not ready, retries failures
with backoff until max_attempts, recovers expired leases, drops the
queue of archived bots and does not blame a delivery when the bot
restarted during the send. Times come from an injectable clock, so the
tests drive retries with a manual clock and a fake inbox.

New RPC methods messages.send, messages.list, deliveries.list and
deliveries.retry, the message.created and delivery.changed
notifications, and the delivery backlog in system.status.

Also fixes the test daemon's botloftd path, which had backspace
characters in it.
Settles how a task chain is found (the sender's deepest open task),
deadlines, late results and the expiry notice, and what each tool
returns. The MCP server speaks both MCP eras: Claude Code 2.1.284
sends server/discover (2026-07-28) first and falls back to initialize
(2025-11-25), as seen with a headless run that fails on an unknown
model before spending tokens. The same run confirmed the
${BOTLOFT_BOT_TOKEN} expansion in mcp.json headers.

Generated settings now allow mcp__botloft, so a bot on its own can use
the crew tools without a permission prompt.
Tasks can be listed by crew, assignee, requester and status, and open
tasks past their deadline found. settle_task moves a task to its new
status and saves the message that reports it in one transaction, and
does nothing when the task is no longer in an expected status.
Params and method name for listing tasks, exported to TypeScript, and
the relative deadline text made public for the tools to reuse.
A bot can send a note or a task to another bot of its crew. A task
continues the chain the sender is working on and is refused past
max_hops. Only the assignee completes a task, also after the deadline,
and the requester gets the result as a message. The courier expires
overdue tasks each cycle and tells the requester. tasks.list lists
them for the app.
POST /mcp gives each bot crew_roster, send_message, complete_task and
my_tasks, authenticated with the bot token of a live generation. It
answers MCP 2026-07-28 (server/discover, header checks, caching hints,
which Claude Code needs to accept the tool list) and the initialize
flow of earlier revisions. Mistakes a model can fix come back as tool
errors it can read.

Generated settings allow mcp__botloft and the rules tell bots to close
every task they receive with complete_task.

Tested with Claude Code 2.1.284: one bot delegated a task to another,
which completed it, and the result came back, with nobody stepping in.
@httpsphl
httpsphl enabled auto-merge September 28, 2026 19:24
@httpsphl
httpsphl merged commit 4c5eef1 into main Sep 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant