fix: build task dependencies in two phases

This commit is contained in:
Haoran
2026-08-19 01:35:08 +08:00
parent 10768e1b74
commit 711249e297
34 changed files with 1102 additions and 443 deletions

View File

@@ -16,7 +16,7 @@
<!-- Title -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Task System — 5 Task Tools + .tasks/ Persistence + blockedBy Dependencies</text>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Task System — 6 Task Tools + .tasks/ Persistence + blockedBy Dependencies</text>
<!-- Legend -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
@@ -46,7 +46,7 @@
<rect x="393" y="80" width="210" height="64" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="498" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">Hooks + TOOL_HANDLERS</text>
<text x="408" y="114" fill="#2563eb" font-size="9">bash · read · write · edit · glob</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · list_tasks</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · update_task · list_tasks</text>
<text x="408" y="140" fill="#0d9488" font-size="9" font-weight="600">get_task · claim_task · complete_task</text>
<!-- Loop back -->
@@ -61,13 +61,13 @@
<!-- Arrow: tools → .tasks/ -->
<path d="M 440 144 L 440 165 L 250 165 L 250 185" fill="none" stroke="#0d9488" stroke-width="1.5" marker-end="url(#arrow-teal)"/>
<text x="320" y="178" fill="#0d9488" font-size="9">create / save / read</text>
<text x="300" y="178" fill="#0d9488" font-size="9">create → ID / update edges / read</text>
<!-- ===== Lifecycle (teal) ===== -->
<rect x="390" y="185" width="330" height="76" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="2"/>
<text x="555" y="205" fill="#134e4a" font-size="11" font-weight="700" text-anchor="middle">Dependency Check + Lifecycle</text>
<text x="408" y="222" fill="#0d9488" font-size="9">can_start: all blockedBy completed?</text>
<text x="408" y="238" fill="#0d9488" font-size="9">claim_task → owner = agent, pending → in_progress</text>
<text x="408" y="222" fill="#0d9488" font-size="9">create_task → runtime ID; update_task → blockedBy</text>
<text x="408" y="238" fill="#0d9488" font-size="9">can_start + claim: all dependencies completed?</text>
<text x="408" y="252" fill="#0d9488" font-size="9">complete_task → completed + unblock downstream</text>
<!-- ===== State machine ===== -->
@@ -90,5 +90,5 @@
<rect x="60" y="366" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="80" y="376" fill="#475569" font-size="10">Base loop: model call + Permission/Hooks + tool dispatch + tool_result</text>
<rect x="60" y="384" width="12" height="10" rx="2" fill="#f0fdfa" stroke="#0d9488" stroke-width="1"/>
<text x="80" y="394" fill="#475569" font-size="10">s10 New: Task dataclass + 5 tools + .tasks/ persistence + blockedBy dependency graph</text>
<text x="80" y="394" fill="#475569" font-size="10">s10 New: Task dataclass + 6 tools + .tasks/ persistence + blockedBy dependency graph</text>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -16,7 +16,7 @@
<!-- タイトル -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Task System — 5 つのタスクツール + .tasks/ 永続化 + blockedBy 依存</text>
<text x="380" y="28" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Task System — 6 つのタスクツール + .tasks/ 永続化 + blockedBy 依存</text>
<!-- 凡例 -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
@@ -46,7 +46,7 @@
<rect x="393" y="80" width="210" height="64" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="498" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">Hooks + TOOL_HANDLERS</text>
<text x="408" y="114" fill="#2563eb" font-size="9">bash · read · write · edit · glob</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · list_tasks</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · update_task · list_tasks</text>
<text x="408" y="140" fill="#0d9488" font-size="9" font-weight="600">get_task · claim_task · complete_task</text>
<!-- ループバック -->
@@ -61,13 +61,13 @@
<!-- 矢印: tools → .tasks/ -->
<path d="M 440 144 L 440 165 L 250 165 L 250 185" fill="none" stroke="#0d9488" stroke-width="1.5" marker-end="url(#arrow-teal)"/>
<text x="320" y="178" fill="#0d9488" font-size="9">create / save / read</text>
<text x="300" y="178" fill="#0d9488" font-size="9">create → ID / update edges / read</text>
<!-- ===== ライフサイクル(ティール) ===== -->
<rect x="390" y="185" width="330" height="76" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="2"/>
<text x="555" y="205" fill="#134e4a" font-size="11" font-weight="700" text-anchor="middle">依存チェック + ライフサイクル</text>
<text x="408" y="222" fill="#0d9488" font-size="9">can_start: blockedBy がすべて completed?</text>
<text x="408" y="238" fill="#0d9488" font-size="9">claim_task → owner = agent, pending → in_progress</text>
<text x="408" y="222" fill="#0d9488" font-size="9">create_task → 実行時 IDupdate_task → blockedBy</text>
<text x="408" y="238" fill="#0d9488" font-size="9">can_start + claim依存がすべて completed?</text>
<text x="408" y="252" fill="#0d9488" font-size="9">complete_task → completed + 下流をアンロック</text>
<!-- ===== 状態マシン ===== -->
@@ -90,5 +90,5 @@
<rect x="60" y="366" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="80" y="376" fill="#475569" font-size="10">基本ループ:モデル呼び出し + Permission/Hooks + ツール分配 + tool_result</text>
<rect x="60" y="384" width="12" height="10" rx="2" fill="#f0fdfa" stroke="#0d9488" stroke-width="1"/>
<text x="80" y="394" fill="#475569" font-size="10">s10 新規Task dataclass + 5 ツール + .tasks/ 永続化 + blockedBy 依存グラフ</text>
<text x="80" y="394" fill="#475569" font-size="10">s10 新規Task dataclass + 6 ツール + .tasks/ 永続化 + blockedBy 依存グラフ</text>
</svg>

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -16,7 +16,7 @@
<!-- Title -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Task System — 5 个任务工具 + .tasks/ 持久化 + blockedBy 依赖</text>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Task System — 6 个任务工具 + .tasks/ 持久化 + blockedBy 依赖</text>
<!-- Legend -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
@@ -46,7 +46,7 @@
<rect x="393" y="80" width="210" height="64" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="498" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">Hooks + TOOL_HANDLERS</text>
<text x="408" y="114" fill="#2563eb" font-size="9">bash · read · write · edit · glob</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · list_tasks</text>
<text x="408" y="128" fill="#0d9488" font-size="9" font-weight="600">create_task · update_task · list_tasks</text>
<text x="408" y="140" fill="#0d9488" font-size="9" font-weight="600">get_task · claim_task · complete_task</text>
<!-- Loop back -->
@@ -61,13 +61,13 @@
<!-- Arrow: tools → .tasks/ -->
<path d="M 440 144 L 440 165 L 250 165 L 250 185" fill="none" stroke="#0d9488" stroke-width="1.5" marker-end="url(#arrow-teal)"/>
<text x="320" y="178" fill="#0d9488" font-size="9">create / save / read</text>
<text x="300" y="178" fill="#0d9488" font-size="9">create → ID / update edges / read</text>
<!-- ===== Lifecycle (teal) ===== -->
<rect x="390" y="185" width="330" height="76" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="2"/>
<text x="555" y="205" fill="#134e4a" font-size="11" font-weight="700" text-anchor="middle">依赖检查 + 生命周期</text>
<text x="408" y="222" fill="#0d9488" font-size="9">can_start: blockedBy 全部 completed?</text>
<text x="408" y="238" fill="#0d9488" font-size="9">claim_task → owner = agent, pending → in_progress</text>
<text x="408" y="222" fill="#0d9488" font-size="9">create_task → 运行时 IDupdate_task → blockedBy</text>
<text x="408" y="238" fill="#0d9488" font-size="9">can_start + claim依赖全部 completed?</text>
<text x="408" y="252" fill="#0d9488" font-size="9">complete_task → completed + 解锁下游</text>
<!-- ===== State machine ===== -->
@@ -90,5 +90,5 @@
<rect x="60" y="366" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="80" y="376" fill="#475569" font-size="10">基础循环:模型调用 + Permission/Hooks + 工具分发 + tool_result</text>
<rect x="60" y="384" width="12" height="10" rx="2" fill="#f0fdfa" stroke="#0d9488" stroke-width="1"/>
<text x="80" y="394" fill="#475569" font-size="10">s10 新增Task dataclass + 5 个工具 + .tasks/ 持久化 + blockedBy 依赖图</text>
<text x="80" y="394" fill="#475569" font-size="10">s10 新增Task dataclass + 6 个工具 + .tasks/ 持久化 + blockedBy 依赖图</text>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -74,7 +74,7 @@
<text x="718" y="407" fill="#0f766e" font-size="9">s14 MCP tools</text>
<path d="M 790 318 L 790 188" fill="none" stroke="#0d9488" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 26 builtins + dynamic mcp__server__tool</text>
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
<text x="95" y="548" fill="#334155" font-size="9">durable work: task tools · cron tools</text>

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -74,7 +74,7 @@
<text x="718" y="407" fill="#0f766e" font-size="9">s14 MCP tools</text>
<path d="M 790 318 L 790 188" fill="none" stroke="#0d9488" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 26 builtins + dynamic mcp__server__tool</text>
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
<text x="95" y="548" fill="#334155" font-size="9">durable work: task tools · cron tools</text>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -94,10 +94,10 @@
<!-- Tool pool -->
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 26 builtins + dynamic mcp__server__tool</text>
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
<text x="95" y="548" fill="#334155" font-size="9">durable work: create/list/get/claim/complete_task · schedule/list/cancel_cron</text>
<text x="95" y="548" fill="#334155" font-size="9">durable work: create/update/list/get/claim/complete_task · schedule/list/cancel_cron</text>
<text x="510" y="512" fill="#334155" font-size="9">team: spawn_teammate · send_message · typed protocols</text>
<text x="510" y="530" fill="#334155" font-size="9">protocol: request_shutdown · request_plan · review_plan</text>
<text x="510" y="548" fill="#334155" font-size="9">workdir/plugin: create_worktree · connect_mcp</text>

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -15,6 +15,20 @@
"description": "各タスクは .tasks/ に id、subject、description、status、owner、blockedBy を持って保存されます。タスクボードはコンテキスト圧縮や再起動を越えて残ります。"
}
},
{
"id": "runtime-owned-task-ids",
"title": "Create Nodes Before Adding Edges",
"description": "create_task returns a host-generated ID. After all nodes exist, update_task uses those returned IDs to add blockedBy edges and rejects invalid or cyclic changes.",
"alternatives": "Asking the model to invent persistent IDs can produce collisions or references that the host never created.",
"zh": {
"title": "先创建节点,再添加边",
"description": "create_task 返回宿主生成的 ID。所有节点创建后update_task 使用这些返回的 ID 添加 blockedBy 边,并拒绝无效或成环的修改。"
},
"ja": {
"title": "ノードを作成してから辺を追加する",
"description": "create_task は host が生成した ID を返します。全ードの作成後、update_task は返された ID で blockedBy の辺を追加し、無効な変更や循環を拒否します。"
}
},
{
"id": "blockedby-dependencies",
"title": "blockedBy Encodes Ordering",

View File

@@ -440,21 +440,23 @@ const CURRENT_FLOW_OVERRIDES: Record<string, FlowDefinition> = {
s10: {
nodes: [
{ id: "start", label: "User Goal", type: "start", x: COL_CENTER, y: 30 },
{ id: "create", label: "create_task", type: "subprocess", x: COL_CENTER, y: 120 },
{ id: "save", label: "Persist JSON\n.tasks/", type: "process", x: COL_CENTER, y: 210 },
{ id: "list", label: "list / get", type: "subprocess", x: COL_RIGHT, y: 300 },
{ id: "deps", label: "blockedBy\ncomplete?", type: "decision", x: COL_CENTER, y: 390 },
{ id: "blocked", label: "Remain Pending", type: "end", x: COL_RIGHT, y: 490 },
{ id: "claim", label: "claim_task\nowner + in_progress", type: "subprocess", x: COL_LEFT, y: 490 },
{ id: "complete", label: "complete_task", type: "subprocess", x: COL_LEFT, y: 590 },
{ id: "unblock", label: "Report\nUnblocked", type: "process", x: COL_CENTER, y: 690 },
{ id: "append", label: "Append Result", type: "process", x: COL_CENTER, y: 780 },
{ id: "create", label: "create_task\n(all nodes)", type: "subprocess", x: COL_CENTER, y: 120 },
{ id: "save", label: "Persist Nodes\nReturn IDs", type: "process", x: COL_CENTER, y: 210 },
{ id: "update", label: "update_task\n(addBlockedBy)", type: "subprocess", x: COL_CENTER, y: 300 },
{ id: "list", label: "list / get", type: "subprocess", x: COL_RIGHT, y: 390 },
{ id: "deps", label: "blockedBy\ncomplete?", type: "decision", x: COL_CENTER, y: 480 },
{ id: "blocked", label: "Remain Pending", type: "end", x: COL_RIGHT, y: 580 },
{ id: "claim", label: "claim_task\nowner + in_progress", type: "subprocess", x: COL_LEFT, y: 580 },
{ id: "complete", label: "complete_task", type: "subprocess", x: COL_LEFT, y: 680 },
{ id: "unblock", label: "Report\nUnblocked", type: "process", x: COL_CENTER, y: 780 },
{ id: "append", label: "Append Result", type: "process", x: COL_CENTER, y: 870 },
],
edges: [
{ from: "start", to: "create" },
{ from: "create", to: "save" },
{ from: "save", to: "list" },
{ from: "save", to: "deps" },
{ from: "save", to: "update" },
{ from: "update", to: "list" },
{ from: "update", to: "deps" },
{ from: "deps", to: "blocked", label: "no" },
{ from: "deps", to: "claim", label: "yes" },
{ from: "claim", to: "complete" },

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -12,36 +12,72 @@
"type": "tool_call",
"toolName": "create_task",
"content": "{\"subject\":\"Run tests\"}",
"annotation": "Tasks are created as JSON files under .tasks/."
"annotation": "The first phase creates task nodes without dependencies."
},
{
"type": "tool_result",
"toolName": "create_task",
"content": "Created task_a1b2c3d4: Run tests",
"annotation": "The host returns the runtime-generated ID after the tool executes."
},
{
"type": "tool_call",
"toolName": "create_task",
"content": "{\"subject\":\"Deploy release\",\"blockedBy\":[\"task_tests\"]}",
"annotation": "blockedBy encodes dependency ordering."
"content": "{\"subject\":\"Deploy release\"}",
"annotation": "Sibling create calls do not guess or predeclare IDs."
},
{
"type": "tool_result",
"toolName": "create_task",
"content": "Created task_5e6f7a8b: Deploy release",
"annotation": "The second node receives a different host-generated ID."
},
{
"type": "tool_call",
"toolName": "update_task",
"content": "{\"task_id\":\"task_5e6f7a8b\",\"addBlockedBy\":[\"task_a1b2c3d4\"]}",
"annotation": "The second phase adds an edge using IDs returned by create_task."
},
{
"type": "tool_result",
"toolName": "update_task",
"content": "Updated task_5e6f7a8b blockedBy: task_a1b2c3d4",
"annotation": "The runtime validates and persists the dependency graph."
},
{
"type": "tool_call",
"toolName": "claim_task",
"content": "{\"task_id\":\"task_deploy\",\"owner\":\"agent\"}",
"content": "{\"task_id\":\"task_5e6f7a8b\"}",
"annotation": "The claim fails until dependencies are complete."
},
{
"type": "tool_result",
"toolName": "claim_task",
"content": "Blocked by: [\"task_tests\"]",
"content": "Blocked by: [\"task_a1b2c3d4\"]",
"annotation": "The task graph prevents premature work."
},
{
"type": "tool_call",
"toolName": "claim_task",
"content": "{\"task_id\":\"task_a1b2c3d4\"}",
"annotation": "The unblocked prerequisite is claimed before work begins."
},
{
"type": "tool_result",
"toolName": "claim_task",
"content": "Claimed task_a1b2c3d4 (Run tests)",
"annotation": "Claim moves the task from pending to in_progress."
},
{
"type": "tool_call",
"toolName": "complete_task",
"content": "{\"task_id\":\"task_tests\"}",
"content": "{\"task_id\":\"task_a1b2c3d4\"}",
"annotation": "Completing a dependency can unblock downstream tasks."
},
{
"type": "tool_result",
"toolName": "complete_task",
"content": "Completed task_tests\nUnblocked: Deploy release",
"content": "Completed task_a1b2c3d4 (Run tests)\nUnblocked: Deploy release",
"annotation": "The harness reports newly available work."
}
]

View File

@@ -30,24 +30,36 @@
"content": "Created task_1a2b3c4d: Refactor authentication",
"annotation": "The runtime-generated task ID is carried into every later operation on this task."
},
{
"type": "tool_call",
"toolName": "create_worktree",
"content": "{\"name\":\"auth-refactor\",\"task_id\":\"task_1a2b3c4d\"}",
"annotation": "The worktree directory is recorded on the task instead of managed as a separate workflow or security sandbox."
},
{
"type": "tool_call",
"toolName": "create_task",
"content": "{\"subject\":\"Update authentication tests\",\"blockedBy\":[\"task_1a2b3c4d\"]}",
"annotation": "The task graph keeps dependent work from starting early."
"content": "{\"subject\":\"Update authentication tests\"}",
"annotation": "The Lead creates every task node before adding graph edges."
},
{
"type": "tool_result",
"toolName": "create_task",
"content": "Created task_5e6f7a8b: Update authentication tests (blockedBy: task_1a2b3c4d)",
"content": "Created task_5e6f7a8b: Update authentication tests",
"annotation": "The second generated ID names the dependent task that the test teammate will later claim."
},
{
"type": "tool_call",
"toolName": "update_task",
"content": "{\"task_id\":\"task_5e6f7a8b\",\"addBlockedBy\":[\"task_1a2b3c4d\"]}",
"annotation": "Only the Lead links tasks using IDs already returned by the runtime."
},
{
"type": "tool_result",
"toolName": "update_task",
"content": "Updated task_5e6f7a8b blockedBy: task_1a2b3c4d",
"annotation": "The dependency is fixed before teammates begin claiming work."
},
{
"type": "tool_call",
"toolName": "create_worktree",
"content": "{\"name\":\"auth-refactor\",\"task_id\":\"task_1a2b3c4d\"}",
"annotation": "The worktree directory is recorded on the task instead of managed as a separate workflow or security sandbox."
},
{
"type": "tool_call",
"toolName": "spawn_teammate",