Skip to content

Dispatch binds facts to a workflow run, not to the task, and never cleans up after itself #1807

Open

Description

@Birfy

Written-by: Chief

The dispatch path works, but it carries facts in the wrong place and leaves every carrier behind. Two changes, both owner-requested on 2026-10-30:

1. Bind the work to the task, not to the workflow

Today a dispatch is one throwaway script per beat — /data/dsh/bot/workspace/disp<HHMM>.mjs — whose body is a hand-written brief string passed to

call('flybot/workflow/assign', { workflowId, taskId, title, brief })

The facts (tip sha, check state, gate reason, what to do step by step) live inside that string, and the string belongs to one run on one lane. So when a run dies and the work moves from the PR lane to the dirty-PR lane — which happened twice this morning (p1795, p1771-replay2/p1771-replay3) — the whole brief is rewritten by hand for the new lane. The same facts get copied a third time into the next beat's script. Nothing owns them.

Wanted: the task is the object that holds the facts; a workflow lane is only an executor. Assigning work means pointing a task at a lane, and the lane reads the task. A re-dispatch to another lane must not need the brief rewritten — the facts already on the task are what the new lane gets.

Shape that would satisfy it:

  • a task carries id, title, brief (the facts), status, and the lane that currently holds it;
  • workflow/assign takes the task id and resolves the brief from the task, so brief is not a per-call argument;
  • re-dispatching to another lane keeps the same task id and the same facts, and appends one line saying which lane it moved to and why (this is the handoff record track_tasks already has a field for);
  • a run reports back onto the task, so the next beat reads the task instead of the run.

2. Keep the filesystem bounded, and settle what matters into Project State

/data/dsh/bot/workspace is now 75 GB in 4278 entries, 2711 of them one-shot .mjs scripts, plus per-session checkout directories of 1–4 GB each (w1712 4.0 G, replay-1727 3.3 G, t1462 2.6 G, replay-1715 2.1 G, …). None of it is ever removed: each beat adds a script, each replay adds a tree.

The information worth keeping is small — what merged, which tip, why a line was re-dispatched, what the owner decided — and it belongs in Project State (PROJECT.md, ROADMAP.md, decisions/<date>-<slug>.md, LESSONS.md), not in a script body nor in a 3 GB checkout.

Wanted:

  • a dispatch script is a transient: it lives in the task's own temp area and is deleted when the task closes (done or blocked), so their count tracks live tasks, not beats elapsed;
  • a session checkout/worktree is deleted when its task closes, with the same rule;
  • the durable facts of a closed task are written once into Project State by the run that closes it — a merged sha, a decision, a lesson — and the task's brief shrinks to a pointer at that record;
  • a guard that reports the two numbers (one-shot scripts, checkout bytes) so unbounded growth is visible instead of being found by accident.

Acceptance

  • A task can be re-dispatched from one lane to another without rewriting its facts, and the second lane sees exactly what the first one saw.
  • After a task closes, no file created for it remains outside Project State.
  • The workspace reports a bounded count and size, and a beat that adds nothing leaves both unchanged.
  • Project State answers "why was this re-dispatched / what merged" without reading a script.

Related: #1775 (route a card by its own handoff) is the first slice of the routing half.

Activity

  1. added
    needs-infoWaiting for the reporter or a maintainer decision
    on Oct 30, 2026
  2. Birfy commented on Oct 30, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    分诊(api.github.com,读取于 2026-10-30T09:45:29Z):park —— 卡在判据 2「范围清楚(文件 + 可执行验收)」。

    盘面:开放 PR 1 条(#1818 → #1809),无一条命中本单;评论 0(无认领、无 pickup-ok);标签空(无 hold)。依赖 #1775 已于 05:43:59Z 经 PR #1791 合入关闭(改动 core/src/host/task-drive.js、core/src/host/tasks.js、core/test/task-handoff-route.test.js、docs/subsystems/core-host.md),改动 1 的方向有已合实现兜底。

    改动 1(task 持 facts、workflow/assign 从 task 取 brief)仓库侧落点可判:

    • core/src/host/tasks.js:task 增 brief;同一 task id 换 lane 时追加一条 handoff(现契约在 :421 校验 from/to/at/reason,brief 字段目前不存在);
    • plugins/workflow/src/service.js:assign/runNamed 改按 taskId 从 task 取 brief(现状 :1513 用 body.brief || body.title,:566 把 brief 取成 task title);
    • plugins/workflow/src/graph.js:492:title/brief 输入改取 task 的 brief;
    • 红先:新增 core/test/task-brief.test.js —— 同一 task 换 lane 后第二次 assign 不传 brief,目标 lane 拿到的 facts 与第一次逐字相同;未改实现时失败;
    • 验收命令:node --test core/test/task-brief.test.js(红)→ npm run verify 全绿 + node scripts/ds-bot-patches.mjs check;
    • 文档:docs/subsystems/workflow.md(assign 参数表)。

    改动 2 现在不可交:/data/dsh/bot/workspace(75 GB / 4278 项 / 2711 个一次性 .mjs、每个 1–4 GB 的 session checkout)不在本仓库,仓库里没有任何代码创建这些脚本或 checkout(core/src/host/tasks.js、task-drive.js 里没有 temp-area/worktree 概念),所以「任务关闭后不留文件」「beat 不新增则两数不变」在 CI 无法验收;照字面派给 Dev,等于去删主机上别的 Bot 的文件——这既不是仓库改动,也碰了 owner 的主机。

    缺什么 / 谁能给:

    • 拆单:改动 1 单开一条(文件如上 + 红先一条 + 上面那条命令),Chief 拆完即可转;
    • 改动 2 改为主机运维条目(Chief 节拍自收,清理范围与同意由 owner 给),或单开一条带只读 guard(报两个数)+ 明确清理动作的 issue,owner 给范围、Chief 给落点。

    本单留 I,拆单后回到本线复判。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-infoWaiting for the reporter or a maintainer decision

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions