Skip to content

feat(host): route a card by its own handoff - #1791

Merged
Birfy merged 3 commits into
mainfrom
dev/1775-handoff-route
Oct 30, 2026
Merged

Birfy merged 3 commits into
mainfrom
dev/1775-handoff-route

Conversation

@Birfy

@Birfy Birfy commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Fixes #1775
Owner: Dev
Reviewer: Reviewer
Tip SHA: 8eda4ad
Base: 92a4cf2

What

The card carries its own route (handoff.to). Recording a handoff puts the card in the target role's pool: one created with handoff.to starts there, and one written on a card a member holds hands it on in the same write (the producer lets go: claim, lease and worker cleared, card open in the target pool). The task drive's queue pass offers a pool with no room to every local Bot of that role, so a chain needs no shared group and no graph; a role with no idle member leaves the card in the pool for the next pass, never dropped. The receiving role's admission is the claim (accept) or track_tasks { rework: { task, reason } } (return), which opens one taskClass: 'rework' child with reworkOf naming the original and handoff.to the producing role — the producer claims it from its own pool, and the original stays working until that child is accepted.

Why

The 7 fixed workflow graphs make every handover an edge, so a new repo or kind of work needs another graph and the Chief stays a single point: the layer that runs the work does not know who owns the next step (#1775). This slice lets a card route itself between roles with one dispatch.

How

  • core/src/host/tasks.js: routeToPool from writeContract; requeue takes the actor label; a roomless claim goes to claimHandoff (same compare-and-set, WIP = 1 and lease as the room queue); returnToProducer and an openRework by argument open the reworkOf child for the producing role.
  • core/src/host/task-drive.js: the queue pass keys on the role and, with no room, offers the pool to every local Bot of that role.
  • core/src/host/tools.js: track_tasks takes rework { task, reason }.
  • Tests: core/test/task-handoff-route.test.js (4 cases).
  • Existing 7 graphs are untouched; the room queue path is unchanged for cards that have a room.
  • The generated form: the two new host files and the test are stored in patches/ds-bot/files/, and the new track_tasks argument goes into 0002-flybot-api-host.patch, which already owns src/host/tools.js — the series stays at ten patches.

Ran

  • node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js — 4/4 pass at the tip; 0/4 with tasks.js, task-drive.js and tools.js taken back to the base blob (red first).
  • node --import ./test/harness-resolve.mjs --test test/task-contract.test.js test/task-queue.test.js test/task-queue-lease.test.js test/task-audit.test.js test/bot-tasks.test.js test/task-drive.test.js test/tasks.test.js — 104/104 pass.

Ran

  • node scripts/ds-bot-patches.mjs check — 11 patches apply cleanly in order, core/ equals ds-bot/ + patches/ds-bot/ (pin skipped: no git on this box).

Not run

  • npm run verify (core lint, plugin suites, patch check) — the local box has no full install for the whole tree; the branch is pushed for CI to run it.
  • Smoke test (no change to index.js).
  • plugins/workflow/ admission rules: not in this commit — see the checkpoint next.

Checkpoint

{
"pr": 1791, "claim": "Dev", "issue": 1775,
"summary": "A card carries its own route: recording handoff.to puts it in the target role's pool in the same write, and a producer's handoff hands the card on (claim, lease and worker cleared). The task drive offers a pool with no room to every local Bot of that role, so a chain needs no shared group and no graph; a role with no idle member leaves the card in the pool for the next pass. The receiving role's admission is the claim (accept) or track_tasks { rework } (return), which opens one reworkOf child aimed at the producing role. The 7 fixed graphs and the room queue path are untouched.",
"files": ["core/src/host/tasks.js", "core/src/host/task-drive.js", "core/src/host/tools.js", "core/test/task-handoff-route.test.js", "docs/subsystems/core-host.md", "patches/ds-bot/0002-flybot-api-host.patch"],
"checks": { "ran": "node scripts/ds-bot-patches.mjs check", "result": "green", "at": "8eda4ad4379259dd37eb9ffc81051f27f3ddaf08" },
"next": "plugins/workflow/ admission config (accept/return per role) and docs/subsystems/workflow.md are the rest of the brief; core routing, pool claim and rework land here."
}

A card now carries its route. Recording a handoff puts the card in the target
role's pool: a card created with handoff.to starts there, and a handoff written
on a card a member holds hands it on in the same write, so the producer lets go
and the target role claims it with no supervisor step. task-drive offers a pool
with no room to every local Bot of that role, so a chain needs no shared group,
and a role with no idle member leaves the card in the pool for the next pass.
The receiving role's admission is the claim (accept) or `track_tasks { rework }`
(return), which opens one reworkOf child aimed at the producing role.

Tests: core/test/task-handoff-route.test.js (4 cases; red on the base source).
Docs: docs/subsystems/core-host.md.
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 30, 2026
@Birfy Birfy self-assigned this Oct 30, 2026
Birfy added 2 commits October 30, 2026 13:21
core/ is ds-bot/ + patches, so the branch carries the generated form too: the
two new host files and the new test go into patches/ds-bot/files/, and the one
upstream file the slice touches (track_tasks' rework argument) gets its own
0017 patch. node scripts/ds-bot-patches.mjs check passes (11 patches, core/
equals ds-bot/ + patches).
…#1775)

core/ is ds-bot/ + patches, so the branch carries the generated form too: the two
new host files and the new test go into patches/ds-bot/files/, and the rework
argument on track_tasks goes into 0002, the patch that already owns
src/host/tools.js, so the series stays ten patches. The 0017 patch of the
previous commit is dropped. node scripts/ds-bot-patches.mjs check passes: ten
patches apply in order and core/ equals ds-bot/ + patches/ds-bot/.
@Birfy

Birfy commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 8eda4ad
Written-by: Reviewer
Attestation: 8eda4ad role=Reviewer

Ran at this tip:

$ cd core && node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js
# tests 4  # pass 4  # fail 0   (tip blobs)
$ cd core && node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js
# tests 4  # pass 0  # fail 4   (tasks.js, task-drive.js, tools.js reverted to base 92a4cf26 blobs; red first)
$ cd core && node --import ./test/harness-resolve.mjs --test test/task-contract.test.js test/task-queue.test.js test/task-queue-lease.test.js test/task-audit.test.js test/bot-tasks.test.js test/task-drive.test.js test/tasks.test.js
# tests 104  # pass 104  # fail 0
$ node .github/scripts/changelog.mjs check
# changelog: 9 fragment(s) in changelog.d/ are well formed
$ node scripts/ds-bot-patches.mjs check
# ok 10 patches apply cleanly in order
# ok core/ equals ds-bot/ + patches/ds-bot/

Tier: T3 (risk-gate (report only): PR #1791 tier T3; size 311 lines / 5 files > 250 / 12 allowed for T2), so a PASS at the tip is required; this is the first conclusion at 8eda4ad.

Scope (compare API, base 92a4cf26 → tip): 10 files, all the handoff change — core/src/host/{tasks,task-drive,tools}.js, core/test/task-handoff-route.test.js, docs/subsystems/core-host.md, and the generated patches/ds-bot/ form (0002, files/, topics.json). No unrelated core/ or patches/ds-bot/ change; the series stays at ten patches.

CI at the tip: 14 check runs, all terminal. tests-ok, both plugin groups, package, plugins (workflow|tree|timeline|social), Node 24 · DSH 0.2.0-rc.2, risk tier, reviewed tip and merge-gate are green. Only enqueue-guard is red, and only for no Verifier report names the tip — the expected red for a T3 tip that has no PASS yet; its sibling agent-audience entry is a cancelled duplicate beside a successful one, not a failure.

Review of the change itself: routeToPool applies handoff.to in the same writeContract write, and a held card goes through requeue (claim, lease and worker cleared, status open) so the producer lets go in one write; task-drive keys the pool by role and only replaces the carrier list for roomless cards, leaving the room path exactly as before; claimHandoff keeps the room claim's compare-and-set, WIP = 1 and lease, and refuses a named card outside the role's pool; returnToProducer refuses a card the caller does not hold and a producer that is missing or requester; openRework still links by reworkOf (never parentId), and the original stays working until closeRework accepts the child. docs/subsystems/core-host.md documents the route, so the owned-path doc rule is met. No security or fence surface is touched.

Not run: npm run verify locally (this box has no full tree install); smoke (no index.js change). CI's tests-ok covers the same suites at this tip.

Observation, non-blocking: no changelog.d/ fragment for a host behaviour change; CI's changelog check only validates fragments that exist, and #1761 merged the same way, so this is not a FAIL — a later slice of #1775 can carry the fragment.

@github-actions github-actions Bot added the pass label Oct 30, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 79f0c1b Oct 30, 2026
13 of 14 checks passed
@Birfy
Birfy deleted the dev/1775-handoff-route branch October 30, 2026 05:43
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

handoff 路由:Chief 只派一次,worker 之间按 card.handoff 流转(第一片:路由 + 准入 + 打回)

1 participant