mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-21 21:03:38 +08:00
fix: build task dependencies in two phases
This commit is contained in:
@@ -79,12 +79,12 @@ loop 自体は同じ構造のままだ。model を呼び、response に `tool_us
|
||||
|
||||
### Tools と Dispatch
|
||||
|
||||
built-in tool pool には 25 個の tool がある:
|
||||
built-in tool pool には 26 個の tool がある:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
create_task, update_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
@@ -127,6 +127,8 @@ S15 には 2 層の plan がある:
|
||||
|
||||
目的は近いが実装は別である。`todo_write` は現在のセッションのチェックリスト全体を置き換え、task record は安定 ID と個別のライフサイクル更新を持つ。次節の独立した `task` ツールは「隔離 subagent を一度派遣する」意味であり、Task System ではない。
|
||||
|
||||
統合 host でもタスクグラフは 2 段階で構築する。Lead はまず全タスクノードを作成し、`create_task` が返した実行時 ID で `update_task` を呼ぶ。チームメイトが使えるのは一覧・Claim・完了だけなので、依存構造は仕事を配る前に Lead が確定する。
|
||||
|
||||
### Subagent と Team
|
||||
|
||||
S15 には 2 種類の delegation がある:
|
||||
@@ -238,4 +240,4 @@ python s15_integrated_harness/code.py
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) は、この host に `Workflow` tool を追加する。Workflow は固定された orchestration path を code に置き、進行状況を記録して同じ run を再開できるようにする。
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
|
||||
|
||||
@@ -79,12 +79,12 @@ The loop keeps the same structure: call the model, check whether the response co
|
||||
|
||||
### Tools and Dispatch
|
||||
|
||||
The built-in tool pool contains 25 tools:
|
||||
The built-in tool pool contains 26 tools:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
create_task, update_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
@@ -127,6 +127,8 @@ The first keeps a single agent from drifting. The second supports team coordinat
|
||||
|
||||
They share an intent, not an implementation: `todo_write` replaces one session checklist, while task records have stable IDs and individual lifecycle updates. The separate `task` tool below means "dispatch one isolated subagent"; it is not the Task System.
|
||||
|
||||
Task graph construction remains two-phase in the integrated host: the Lead creates all task nodes first, then calls `update_task` with the runtime IDs returned by `create_task`. Teammates receive only list, claim, and complete operations, so dependency structure is fixed by the Lead before work is distributed.
|
||||
|
||||
### Subagents and Teams
|
||||
|
||||
S15 has two kinds of delegation:
|
||||
@@ -238,4 +240,4 @@ Watch for:
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) adds a `Workflow` tool to this host. A workflow keeps a fixed orchestration path in code and records progress so the same run can resume.
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
|
||||
|
||||
@@ -79,12 +79,12 @@ S15 不再引入新机制,而是把前面各章的组件集成到同一个 har
|
||||
|
||||
### 工具与分发
|
||||
|
||||
内置工具池包含 25 个工具:
|
||||
内置工具池包含 26 个工具:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
create_task, update_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
@@ -127,6 +127,8 @@ S15 同时保留两层计划:
|
||||
|
||||
两者目标相近,但实现不同:`todo_write` 整表替换当前会话清单,task record 则有稳定 ID 和单条生命周期更新。下面单独出现的 `task` 工具表示“一次性派发隔离 subagent”,不是 Task System。
|
||||
|
||||
集成宿主中的任务图仍采用两阶段构建:Lead 先创建所有任务节点,再使用 `create_task` 返回的运行时 ID 调用 `update_task`。队友只能列举、认领和完成任务,因此依赖结构由 Lead 在分发工作前确定。
|
||||
|
||||
### 子 agent 与团队
|
||||
|
||||
S15 有两种 delegation:
|
||||
@@ -238,4 +240,4 @@ python s15_integrated_harness/code.py
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) 会在这个 host 中加入 `Workflow` 工具。Workflow 把固定的编排路径写在代码中,并记录运行进度,使同一次运行可以继续执行。
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
|
||||
|
||||
@@ -205,16 +205,11 @@ def _task_path(task_id: str) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def create_task(subject: str, description: str = "",
|
||||
blockedBy: list[str] | None = None) -> Task:
|
||||
def create_task(subject: str, description: str = "") -> Task:
|
||||
subject = subject.strip()
|
||||
if not subject:
|
||||
raise ValueError("Task subject cannot be empty")
|
||||
dependencies = list(dict.fromkeys(blockedBy or []))
|
||||
with task_store_lock():
|
||||
for dependency in dependencies:
|
||||
if not _task_path(dependency).is_file():
|
||||
raise ValueError(f"Dependency not found: {dependency}")
|
||||
for _ in range(100):
|
||||
task = Task(
|
||||
id=f"task_{secrets.token_hex(4)}",
|
||||
@@ -222,7 +217,7 @@ def create_task(subject: str, description: str = "",
|
||||
description=description,
|
||||
status="pending",
|
||||
owner=None,
|
||||
blockedBy=dependencies,
|
||||
blockedBy=[],
|
||||
)
|
||||
try:
|
||||
with _task_path(task.id).open("x", encoding="utf-8") as handle:
|
||||
@@ -233,6 +228,55 @@ def create_task(subject: str, description: str = "",
|
||||
raise RuntimeError("Could not allocate a unique task ID")
|
||||
|
||||
|
||||
def _task_depends_on(task_id: str, target_id: str) -> bool:
|
||||
"""Return whether task_id transitively depends on target_id."""
|
||||
pending = [task_id]
|
||||
visited = set()
|
||||
while pending:
|
||||
current = pending.pop()
|
||||
if current == target_id:
|
||||
return True
|
||||
if current in visited:
|
||||
continue
|
||||
visited.add(current)
|
||||
pending.extend(load_task(current).blockedBy)
|
||||
return False
|
||||
|
||||
|
||||
def update_task(task_id: str, addBlockedBy: list[str]) -> Task:
|
||||
"""Add dependency edges after create_task has returned real task IDs."""
|
||||
if not isinstance(addBlockedBy, list):
|
||||
raise ValueError("addBlockedBy must be a list of task IDs")
|
||||
|
||||
with task_store_lock():
|
||||
task = load_task(task_id)
|
||||
if task.status != "pending" or task.owner is not None:
|
||||
raise ValueError(
|
||||
f"Task {task_id} dependencies can only be updated while "
|
||||
"pending and unowned"
|
||||
)
|
||||
|
||||
dependencies = list(dict.fromkeys(addBlockedBy))
|
||||
for dependency in dependencies:
|
||||
if dependency == task_id:
|
||||
raise ValueError("Task cannot depend on itself")
|
||||
if not _task_path(dependency).is_file():
|
||||
raise ValueError(f"Dependency not found: {dependency}")
|
||||
if dependency not in task.blockedBy and _task_depends_on(
|
||||
dependency, task_id
|
||||
):
|
||||
raise ValueError(
|
||||
f"Dependency cycle detected: {task_id} -> {dependency}"
|
||||
)
|
||||
|
||||
task.blockedBy.extend(
|
||||
dependency for dependency in dependencies
|
||||
if dependency not in task.blockedBy
|
||||
)
|
||||
save_task(task)
|
||||
return task
|
||||
|
||||
|
||||
def save_task(task: Task):
|
||||
with task_store_lock():
|
||||
path = _task_path(task.id)
|
||||
@@ -737,12 +781,18 @@ PROMPT_SECTIONS = {
|
||||
"identity": "You are a coding agent. Act, don't explain.",
|
||||
"tools": "Available tools: bash, read_file, write_file, edit_file, glob, "
|
||||
"todo_write, task, load_skill, compact, "
|
||||
"create_task, list_tasks, get_task, claim_task, complete_task, "
|
||||
"create_task, update_task, list_tasks, get_task, claim_task, "
|
||||
"complete_task, "
|
||||
"schedule_cron, list_crons, cancel_cron, "
|
||||
"spawn_teammate, list_teammates, send_message, "
|
||||
"request_shutdown, request_plan, review_plan, "
|
||||
"create_worktree, "
|
||||
"connect_mcp. MCP tools are prefixed mcp__{server}__{tool}.",
|
||||
"tasks": (
|
||||
"Create all task nodes first. Only after create_task returns "
|
||||
"runtime-generated IDs, use update_task with those exact IDs to add "
|
||||
"dependencies. Only the Lead changes task dependencies."
|
||||
),
|
||||
"teams": (
|
||||
"When parallel work would help, first propose a small team with clear "
|
||||
"responsibilities and wait for the user's confirmation. Do not call "
|
||||
@@ -777,6 +827,7 @@ def assemble_system_prompt(context: dict) -> str:
|
||||
# memory, skill catalog, MCP state, and active teammates become visible.
|
||||
sections = [PROMPT_SECTIONS["identity"],
|
||||
PROMPT_SECTIONS["tools"],
|
||||
PROMPT_SECTIONS["tasks"],
|
||||
PROMPT_SECTIONS["teams"],
|
||||
PROMPT_SECTIONS["workspace"],
|
||||
PROMPT_SECTIONS["memory"],
|
||||
@@ -2620,12 +2671,22 @@ def run_create_worktree(name: str, task_id: str) -> str:
|
||||
|
||||
# -- Basic Tool Handlers --
|
||||
|
||||
def run_create_task(subject: str, description: str = "",
|
||||
blockedBy: list[str] | None = None) -> str:
|
||||
task = create_task(subject, description, blockedBy)
|
||||
deps = f" (blockedBy: {', '.join(blockedBy)})" if blockedBy else ""
|
||||
print(f" \033[34m[create] {task.subject}{deps}\033[0m")
|
||||
return f"Created {task.id}: {task.subject}{deps}"
|
||||
def run_create_task(subject: str, description: str = "") -> str:
|
||||
task = create_task(subject, description)
|
||||
print(f" \033[34m[create] {task.subject}\033[0m")
|
||||
return f"Created {task.id}: {task.subject}"
|
||||
|
||||
|
||||
def run_update_task(task_id: str, addBlockedBy: list[str]) -> str:
|
||||
try:
|
||||
task = update_task(task_id, addBlockedBy)
|
||||
except ValueError as exc:
|
||||
return f"Error: {exc}"
|
||||
except FileNotFoundError:
|
||||
return f"Error: Task {task_id} not found"
|
||||
dependencies = ", ".join(task.blockedBy) or "(none)"
|
||||
print(f" \033[34m[update] {task.subject} blockedBy: {dependencies}\033[0m")
|
||||
return f"Updated {task.id} blockedBy: {dependencies}"
|
||||
|
||||
|
||||
def run_list_tasks() -> str:
|
||||
@@ -2745,13 +2806,26 @@ BUILTIN_TOOLS = [
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {"focus": {"type": "string"}},
|
||||
"required": []}},
|
||||
{"name": "create_task", "description": "Create a task.",
|
||||
{"name": "create_task",
|
||||
"description": "Create a task and return its runtime-generated ID.",
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {"subject": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"blockedBy": {"type": "array",
|
||||
"items": {"type": "string"}}},
|
||||
"required": ["subject"]}},
|
||||
"description": {"type": "string"}},
|
||||
"required": ["subject"],
|
||||
"additionalProperties": False}},
|
||||
{"name": "update_task",
|
||||
"description": "Add dependencies using IDs returned by create_task.",
|
||||
"input_schema": {"type": "object",
|
||||
"properties": {
|
||||
"task_id": {"type": "string",
|
||||
"pattern": "^task_[0-9a-f]{8}$"},
|
||||
"addBlockedBy": {
|
||||
"type": "array",
|
||||
"items": {"type": "string",
|
||||
"pattern": "^task_[0-9a-f]{8}$"},
|
||||
"minItems": 1}},
|
||||
"required": ["task_id", "addBlockedBy"],
|
||||
"additionalProperties": False}},
|
||||
{"name": "list_tasks", "description": "List all tasks.",
|
||||
"input_schema": {"type": "object", "properties": {}, "required": []}},
|
||||
{"name": "get_task", "description": "Get full task details.",
|
||||
@@ -2848,7 +2922,8 @@ BUILTIN_HANDLERS = {
|
||||
"glob": run_agent_glob,
|
||||
"todo_write": run_todo_write, "task": spawn_subagent,
|
||||
"load_skill": load_skill,
|
||||
"create_task": run_create_task, "list_tasks": run_list_tasks,
|
||||
"create_task": run_create_task, "update_task": run_update_task,
|
||||
"list_tasks": run_list_tasks,
|
||||
"get_task": run_get_task,
|
||||
"claim_task": run_claim_task, "complete_task": run_complete_task,
|
||||
"schedule_cron": run_schedule_cron,
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
Reference in New Issue
Block a user