feat(teamharness): task state transition engine (table + history + events + progress) - #1233
Draft
LUOSENGWA wants to merge 5 commits into
Draft
feat(teamharness): task state transition engine (table + history + events + progress)#1233LUOSENGWA wants to merge 5 commits into
LUOSENGWA wants to merge 5 commits into
Conversation
…tack integration - submit sync-failure: agentscope-ai#1183 _sync_failure_result contract (statePersisted) + agentscope-ai#1206 v2 withheld-notification clause; guard before notification - reused-digest submit branch returns the reused completion notification - _accept_task_result: resolve outstanding attention in place on task_meta (separate read-modify-write was clobbered by the terminal status write) - test-taskflow.rb: fix latent heredoc escape bug (Result body \n), role env toggles for agentscope-ai#1183 runtime-identity-first role, mc fail-sync hook for per-file sync (mc cp), vocabulary PARTIAL/FAILED -> INTERRUPTED, changed-status resubmit -> digest-fence conflict expectation - test-projectflow.rb: fixture runtime.yaml gains member role leader
…ess)
- contracts/task-transitions.json: single source of truth for task
state transitions, loaded and asserted by both the Python write side
and the Go read-side tests (cross-language drift guard).
- server.py: TRANSITIONS/TERMINAL constants + _assert_transition +
_transition_task() as the single mutation entry (validate -> status ->
history (cap 50) -> task meta + project node synced in one batch).
delegate/ack/submit/cancel/accept refactored onto it; ack/submit
tightened to from in {assigned, in_progress}, accept to from ==
submitted (out-of-order transitions now return structured errors with
the corrective action). accept_task_result now also updates the task
meta (previously only the project meta was written).
- server.py: new report_progress action (worker/remote-member; note
required, <=200 chars truncated+flagged; records a from==to history
entry without changing state or sending a room notification).
- controller: taskDetail now passes through the task meta history;
new GET /api/v1/projects/{id}/events read-time aggregation endpoint
(ascending timeline, opaque offset cursor, limit 50/200, same
auth/scope rules as workflow+history); CancelTask records the
transition (actor=authzActor) in the same read-modify-write batch,
with no duplicate entry on retry-convergence.
- tests: new Ruby test-transition-table.rb (fixture consistency, full
lifecycle with chain integrity, out-of-order rejection, idempotent
re-entry, cap, report_progress branches, cancel trace); Go tests for
fixture vs isTerminalTaskStatus consistency, history pass-through
(absent/malformed), events endpoint (sort/cursor/limit/empty/404/
denied/cross-scope), cancel trace; test-taskflow.rb secret-artifact
fixture fixed to delegate with an assignee (it relied on the old
permissive submit guard).
- docs: transition engine design doc, runtime design update (table +
history + prepared state), usage docs EN/ZH (events endpoint +
history field), task-execution skill progress guidance.
LUOSENGWA
force-pushed
the
feat/workflow-transition-engine
branch
from
September 10, 2026 04:08
0036950 to
95bffcd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Task state transitions move from "each call site hand-writes
statuswithuneven guards" to one table + one entry point + one auditable history:
plugins/teamharness/contracts/task-transitions.jsondefines the legalstate graph. The Python write side (
TRANSITIONSconstant +_assert_transition) and the Go read-side tests load the same file andassert consistency, so cross-language drift fails in CI.
_transition_task()— single mutation entry — every task state changegoes through: table validation → status write →
history[]append (cap 50,oldest dropped) → task meta + project node synced in one batch. All five
MCP call sites (
delegate_task/ack_task/submit_task/accept_task_result/cancel_task) are refactored onto it.transitions that were silently accepted now return structured errors with
the corrective action in the message.
accept_task_resultnow updates the task meta — previously it onlywrote the project meta, so the task meta and the graph node could diverge
forever. Closed.
report_progress(new action) — workers self-report progress on longtasks: a
from == tohistory entry (actionprogress), note required(≤ 200 chars, truncated + flagged), no state change, no room
notification.
tasks_detail[].historypass-through on?includeTasks=true; newGET /api/v1/projects/{id}/events?limit=&cursor=(read-time aggregation of all task histories into one ascending timeline;
no new storage, no write-side hook);
CancelTaskrecords the transition(actor = authz actor) in the same read-modify-write batch, with no
duplicate entry on the retry-convergence path.
Behavior changes (explicit)
ack_taskfromplanned/preparedin_progress)ack_task: task is 'planned'; delegate_task it firstsubmit_taskfromplanned/preparedsubmit_task: task is 'planned'; ack_task it firstaccept_task_resultfrom any non-terminalsubmittedaccept_task_resultwrite setcancel_task(MCP + Controller)history[]entry (reason innote)history[]field (ts/from/to/action/actor/note?, cap 50)GET .../workflow?includeTasks=truetasks_detail[]history[]per task (omitempty; older metas unaffected)GET .../eventslimit50 (cap 200)report_progressSame-state re-entry (e.g. a repeated
ack_taskonin_progress) stays alegal idempotent no-op: no error, no history entry.
The tightenings are safe for well-behaved agents: the skill contract already
orders delegate → ack → execute → submit, and the error message for any
out-of-order attempt is the fix instruction.
What's included
plugins/teamharness/contracts/task-transitions.json(new) — states /terminal / transitions
plugins/teamharness/mcp/server.py—TRANSITIONS/TERMINAL_TASK_STATUSESconstants,
_assert_transition,_transition_task(+_append_transition_history,actor =
role:account), the five call-site refactorings,report_progress(action enum + schema + routing)
agentteams-controller—taskTransitiontype +taskDetail.History(omitempty, malformed entries skipped);
GetProjectEventshandler +collectProjectEvents(concurrent 8-read, same scope/ownership rules asreadTasksDetail, no cross-scope fallback); route registration;CancelTaskhistory entry (skipped on retry-convergence)test-transition-table.rb(new; registered inrun-integration-tests.sh): fixture consistency, full lifecycle withhistory-chain integrity (each entry starts where the previous ended,
non-decreasing RFC 3339 timestamps,
actorformat), out-of-orderrejection with guidance strings, idempotent re-entry, cap 50 drop-oldest,
report_progressbranches (role gate / empty note / truncation flag / nonotification / state gates), cancel trace; Go: fixture vs
isTerminalTaskStatusconsistency, history pass-through (absent /malformed), events endpoint (sort + tie-break / cursor continuation /
limit bounds + cap / empty project / 404 / denied→404 / cross-scope no
fallback), cancel trace incl. retry-convergence no-duplicate
test-taskflow.rbminimal adaptation (1 fixture): thesecret-artifact case delegated a task with no assignee, leaving it
prepared(no notification target); it then submitted straight fromprepared, which the old permissive guard silently accepted. It nowdelegates with an explicit
assignedToand walks the full lifecycle.docs/design/teamharness/task-transition-engine.md;project-task-runtime-design.md(table + history +preparedstate);docs/usage/project-workflow-api.md+ zh-cn (events endpoint,historyfield);
task-executionskill (one progress self-reporting paragraph)Tests
test-transition-table.rbgreen;test-taskflow.rbfull regressiongreen;
test-projectflow.rb/test-contracts.rb/test-server.rb/test-message.rb/test-filesync.rbgreentest_continuation.py66/66; copawtest_taskflow_tool.py99/99go build+go test ./...green (20 packages; 7 new server tests, 0regressions)
run-integration-tests.shruns clean through the TeamHarness suite; theonly failing step on this stack is a pre-existing upstream CLI contract
test (loongsuite probe definition), present on the stack base commit
before any change in this PR
Compatibility
history[]is a new task-meta field;readers without it (old controllers, the copaw worker) are unaffected
tightenings listed above
.../historyor.../history/{timestamp}CancelTaskwrite volume is unchanged (the meta was alreadyread-modify-written; the entry is added in the same batch)
Known limitations (recorded, not fixed here)
(pull-before-write vs ETag conditional write).
history[]rides the samewrite transaction as the existing task fields — no new race, same
exposure.
clients poll. Project-level intervention events stay on the
/historysnapshot endpoint (the two are complementary and documented as such).
binary share no deployment surface) — the shared artifact is the fixture
plus the two-sided test assertions.
Worker-runtime cross-check (#1223 evaluation requirement)
copaw/src/copaw_worker/task.pyis the worker-side local state machine overthe same TaskMeta model. Transition-point comparison after this PR:
task.py(worker local){completed, revision, blocked, cancelled}terminal)SUCCESS/SUCCESS_WITH_NOTES/REVISION_NEEDED/BLOCKED/INTERRUPTEDdelegate_taskprepared, commitsassignedafter the notification landsack_taskfrom ∈ {assigned, in_progress}from ∈ {assigned, in_progress}submit_tasksubmittedfrom ∈ {assigned, in_progress}planned/prepared→submitted. Direction is safe (the MCP side refuses states the worker side merely tolerates); the tightening guidance applies. Tracked as follow-up, not fixed here.accept_task_resultsubmitted(terminal idempotent replay allowed)from == submittedcancel_taskhistory[]摘要
任务状态转换从"各调用点手改
status、守卫宽严不一"升级为一张表 + 一个入口 + 一条可审计历史——工作流引擎设计(#1223)的第 2 部分(引擎侧)。plugins/teamharness/contracts/task-transitions.json定义合法状态图;Python 写侧(TRANSITIONS常量 +_assert_transition)与 Go 读侧测试加载同一文件并断言一致,跨语言漂移在 CI 暴露。_transition_task()单一变更入口 —— 转换表校验 → 写 status → 追加history[](上限 50,丢最旧)→ task meta 与 project 节点同批同步。五个 MCP 调用点全部收口。accept_task_result现在同步更新 task meta —— 原先只写 project meta,task meta 与图节点可永久分叉,缺口已修复。report_progress(新 action) —— 长任务进度自报:from == to的 history 条目(actionprogress),note 必填(≤200 字符,超长截断+标记),不改状态、不发房间通知。?includeTasks=true透传tasks_detail[].history;新端点GET /api/v1/projects/{id}/events?limit=&cursor=(读时聚合全部任务 history 成升序时间线,零新存储、无写侧钩子);CancelTask在同一 read-modify-write 批次写入 history 条目(actor = 授权主体,重试收敛路径不重复记)。行为变更(明示)
ack_task自planned/preparedin_progress)ack_task: task is 'planned'; delegate_task it firstsubmit_task自planned/preparedsubmit_task: task is 'planned'; ack_task it firstaccept_task_result自任意非终态submitted可验收accept_task_result写入集cancel_task(MCP + Controller)history[]条目(reason 入note)history[]字段(ts/from/to/action/actor/note?,上限 50)GET .../workflow?includeTasks=truetasks_detail[]history[](omitempty,旧 meta 不受影响)GET .../eventslimit50(上限 200)report_progress同状态重入(如
in_progress上重复ack_task)仍是合法幂等 no-op:不报错、不记 history。收紧对合规 agent 无影响:skill 契约本就规定 delegate → ack → 执行 → submit 顺序,越序时错误消息即修复指引。
包含内容
plugins/teamharness/contracts/task-transitions.json(新)—— states / terminal / transitionsplugins/teamharness/mcp/server.py——TRANSITIONS/TERMINAL_TASK_STATUSES常量、_assert_transition、_transition_task(+_append_transition_history,actor =role:account)、五个调用点重构、report_progress(action 枚举 + schema + 路由)agentteams-controller——taskTransition类型 +taskDetail.History(omitempty,畸形条目跳过);GetProjectEvents处理器 +collectProjectEvents(并发 8 读,与readTasksDetail同 scope/所有权规则,不跨 scope 回退);路由注册;CancelTaskhistory 条目(重试收敛跳过)test-transition-table.rb(新,已注册进run-integration-tests.sh):fixture 一致性、全生命周期 history 链完整性(条目首尾相接、RFC 3339 时间戳非递减、actor格式)、越序拒绝(含引导文案)、幂等重入、cap 50 丢最旧、report_progress全分支(角色门 / 空 note / 截断标记 / 无通知 / 状态门)、cancel 留痕;Go:fixture 与isTerminalTaskStatus一致性、history 透传(缺失/畸形)、events 端点(排序 + 同秒 tie-break / 游标续读 / limit 边界 + 上限 / 空项目 / 404 / denied→404 / 跨 scope 不回退)、cancel 留痕(含重试收敛不重复)test-taskflow.rb最小适配(1 个 fixture):secret-artifact 用例原以"无 assignee 的 delegate 停在prepared也能 submit"的旧宽行为通过;现补assignedTo走完整生命周期(注释写明原因)docs/design/teamharness/task-transition-engine.md;project-task-runtime-design.md(转换表 + history +prepared状态);docs/usage/project-workflow-api.md+ zh-cn(events 端点、history字段);task-executionskill(进度自报一段)测试
test-transition-table.rb绿;test-taskflow.rb全量回归绿;test-projectflow.rb/test-contracts.rb/test-server.rb/test-message.rb/test-filesync.rb绿test_continuation.py66/66;copawtest_taskflow_tool.py99/99go build+go test ./...绿(20 包;server 包新增 7 个测试,0 回归)run-integration-tests.shTeamHarness 套件全跑通;本栈上唯一失败是 upstream 既有的 CLI 契约测试(loongsuite 探针定义),在本 PR 改动前的栈基线 commit 上即存在兼容性
history[]是 task meta 新字段,旧读者(旧 controller、copaw worker)不受影响.../history/.../history/{timestamp}冲突CancelTask写入量不变(meta 本就 read-modify-write,条目加在同一批次)已知限制(登记,不在本 PR 修复)
history[]与现有 task 字段同写事务、同待遇——不新增竞态/history快照端点(两者互补,文档写明)Worker runtime 对照(#1223 评估要求)
copaw/src/copaw_worker/task.py是 worker 侧基于同一 TaskMeta 模型的本地状态机。本 PR 后的转换点对照:task.py(worker 本地){completed, revision, blocked, cancelled}terminal)SUCCESS/SUCCESS_WITH_NOTES/REVISION_NEEDED/BLOCKED/INTERRUPTEDdelegate_taskprepared,通知落地后提交assignedack_taskfrom ∈ {assigned, in_progress}from ∈ {assigned, in_progress}submit_tasksubmitted不重提from ∈ {assigned, in_progress}planned/prepared→submitted。方向安全(MCP 侧拒绝的是 worker 侧"容忍"的状态),收紧引导适用。作为 follow-up 跟踪,不在本 PR 修复accept_task_resultsubmitted(终态幂等重放放行)from == submittedcancel_taskhistory[]