From 324bca225e46c678caba51a700d4bd241ee3ae47 Mon Sep 17 00:00:00 2001 From: JIA-ZI-LONG <1497176841@qq.com> Date: Tue, 24 Mar 2026 20:39:34 +0800 Subject: [PATCH] refactor(tasks): remove redundant blocks field, add remove_blocked_by MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background: The TaskManager in s07_task_system.py maintained both `blockedBy` (incoming edges) and `blocks` (outgoing edges) for each node. The design intended that calling update() once with both `add_blocked_by` and `add_blocks` would update edges bidirectionally. However, this design has two problems: 1. LLM never actually uses `add_blocks`: Debugging showed that whether creating task chains (3 creates followed by updates to connect) or inserting new nodes (A→B→C becoming A→B→D→C), the LLM always calls update() separately for each node, only passing `add_blocked_by`. The `add_blocks` parameter was never used - it's dead code. 2. Even if LLM followed the design, the graph would be incomplete: When handling `add_blocks`, update() syncs to the next node's `blockedBy`, but never updates the previous node's `blocks`. This means edge records are unidirectional and missing, making `blocks` useless for understanding the graph structure. Conclusion: The `blocks` field is neither used by LLM nor can it stay consistent. It should be removed. Execution scheduling only needs `blockedBy` (the existing _clear_dependency also never reads `blocks`). Added `remove_blocked_by` to support edge removal when inserting nodes. Changes: - Remove `blocks` field from task creation - Remove `add_blocks` parameter from update() and its handling logic - Add `remove_blocked_by` parameter to support edge removal during graph rewrites - Update tool handlers and JSON schema accordingly - Update documentation (en/ja/zh) to reflect the changes New usage example (inserting D between B→C): TASKS.create("D") TASKS.update(D_id, add_blocked_by=[B_id]) TASKS.update(C_id, add_blocked_by=[D_id], remove_blocked_by=[B_id]) --- agents/s07_task_system.py | 21 ++++++--------------- docs/en/s07-task-system.md | 13 ++++++++----- docs/ja/s07-task-system.md | 13 ++++++++----- docs/zh/s07-task-system.md | 13 ++++++++----- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/agents/s07_task_system.py b/agents/s07_task_system.py index 7689be42b..9fd077ab5 100644 --- a/agents/s07_task_system.py +++ b/agents/s07_task_system.py @@ -67,7 +67,7 @@ def _save(self, task: dict): def create(self, subject: str, description: str = "") -> str: task = { "id": self._next_id, "subject": subject, "description": description, - "status": "pending", "blockedBy": [], "blocks": [], "owner": "", + "status": "pending", "blockedBy": [], "owner": "", } self._save(task) self._next_id += 1 @@ -77,7 +77,7 @@ def get(self, task_id: int) -> str: return json.dumps(self._load(task_id), indent=2) def update(self, task_id: int, status: str = None, - add_blocked_by: list = None, add_blocks: list = None) -> str: + add_blocked_by: list = None, remove_blocked_by: list = None) -> str: task = self._load(task_id) if status: if status not in ("pending", "in_progress", "completed"): @@ -88,17 +88,8 @@ def update(self, task_id: int, status: str = None, self._clear_dependency(task_id) if add_blocked_by: task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) - if add_blocks: - task["blocks"] = list(set(task["blocks"] + add_blocks)) - # Bidirectional: also update the blocked tasks' blockedBy lists - for blocked_id in add_blocks: - try: - blocked = self._load(blocked_id) - if task_id not in blocked["blockedBy"]: - blocked["blockedBy"].append(task_id) - self._save(blocked) - except ValueError: - pass + if remove_blocked_by: + task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] self._save(task) return json.dumps(task, indent=2) @@ -182,7 +173,7 @@ def run_edit(path: str, old_text: str, new_text: str) -> str: "write_file": lambda **kw: run_write(kw["path"], kw["content"]), "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]), "task_create": lambda **kw: TASKS.create(kw["subject"], kw.get("description", "")), - "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("addBlockedBy"), kw.get("addBlocks")), + "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("addBlockedBy"), kw.get("removeBlockedBy")), "task_list": lambda **kw: TASKS.list_all(), "task_get": lambda **kw: TASKS.get(kw["task_id"]), } @@ -199,7 +190,7 @@ def run_edit(path: str, old_text: str, new_text: str) -> str: {"name": "task_create", "description": "Create a new task.", "input_schema": {"type": "object", "properties": {"subject": {"type": "string"}, "description": {"type": "string"}}, "required": ["subject"]}}, {"name": "task_update", "description": "Update a task's status or dependencies.", - "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}, "addBlockedBy": {"type": "array", "items": {"type": "integer"}}, "addBlocks": {"type": "array", "items": {"type": "integer"}}}, "required": ["task_id"]}}, + "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}, "addBlockedBy": {"type": "array", "items": {"type": "integer"}}, "removeBlockedBy": {"type": "array", "items": {"type": "integer"}}}, "required": ["task_id"]}}, {"name": "task_list", "description": "List all tasks with status summary.", "input_schema": {"type": "object", "properties": {}}}, {"name": "task_get", "description": "Get full details of a task by ID.", diff --git a/docs/en/s07-task-system.md b/docs/en/s07-task-system.md index 0eece3933..da331d19b 100644 --- a/docs/en/s07-task-system.md +++ b/docs/en/s07-task-system.md @@ -14,7 +14,7 @@ Without explicit relationships, the agent can't tell what's ready, what's blocke ## Solution -Promote the checklist into a **task graph** persisted to disk. Each task is a JSON file with status, dependencies (`blockedBy`), and dependents (`blocks`). The graph answers three questions at any moment: +Promote the checklist into a **task graph** persisted to disk. Each task is a JSON file with status and dependencies (`blockedBy`). The graph answers three questions at any moment: - **What's ready?** -- tasks with `pending` status and empty `blockedBy`. - **What's blocked?** -- tasks waiting on unfinished dependencies. @@ -59,8 +59,7 @@ class TaskManager: def create(self, subject, description=""): task = {"id": self._next_id, "subject": subject, - "status": "pending", "blockedBy": [], - "blocks": [], "owner": ""} + "status": "pending", "blockedBy": [], "owner": ""} self._save(task) self._next_id += 1 return json.dumps(task, indent=2) @@ -81,12 +80,16 @@ def _clear_dependency(self, completed_id): ```python def update(self, task_id, status=None, - add_blocked_by=None, add_blocks=None): + add_blocked_by=None, remove_blocked_by=None): task = self._load(task_id) if status: task["status"] = status if status == "completed": self._clear_dependency(task_id) + if add_blocked_by: + task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) + if remove_blocked_by: + task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] self._save(task) ``` @@ -110,7 +113,7 @@ From s07 onward, the task graph is the default for multi-step work. s03's Todo r |---|---|---| | Tools | 5 | 8 (`task_create/update/list/get`) | | Planning model | Flat checklist (in-memory) | Task graph with dependencies (on disk) | -| Relationships | None | `blockedBy` + `blocks` edges | +| Relationships | None | `blockedBy` edges | | Status tracking | Done or not | `pending` -> `in_progress` -> `completed` | | Persistence | Lost on compression | Survives compression and restarts | diff --git a/docs/ja/s07-task-system.md b/docs/ja/s07-task-system.md index 77eeb2448..db8807e47 100644 --- a/docs/ja/s07-task-system.md +++ b/docs/ja/s07-task-system.md @@ -14,7 +14,7 @@ s03のTodoManagerはメモリ上のフラットなチェックリストに過ぎ ## 解決策 -フラットなチェックリストをディスクに永続化する**タスクグラフ**に昇格させる。各タスクは1つのJSONファイルで、ステータス・前方依存(`blockedBy`)・後方依存(`blocks`)を持つ。タスクグラフは常に3つの問いに答える: +フラットなチェックリストをディスクに永続化する**タスクグラフ**に昇格させる。各タスクは1つのJSONファイルで、ステータスと前方依存(`blockedBy`)を持つ。タスクグラフは常に3つの問いに答える: - **何が実行可能か?** -- `pending`ステータスで`blockedBy`が空のタスク。 - **何がブロックされているか?** -- 未完了の依存を待つタスク。 @@ -59,8 +59,7 @@ class TaskManager: def create(self, subject, description=""): task = {"id": self._next_id, "subject": subject, - "status": "pending", "blockedBy": [], - "blocks": [], "owner": ""} + "status": "pending", "blockedBy": [], "owner": ""} self._save(task) self._next_id += 1 return json.dumps(task, indent=2) @@ -81,12 +80,16 @@ def _clear_dependency(self, completed_id): ```python def update(self, task_id, status=None, - add_blocked_by=None, add_blocks=None): + add_blocked_by=None, remove_blocked_by=None): task = self._load(task_id) if status: task["status"] = status if status == "completed": self._clear_dependency(task_id) + if add_blocked_by: + task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) + if remove_blocked_by: + task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] self._save(task) ``` @@ -110,7 +113,7 @@ s07以降、タスクグラフがマルチステップ作業のデフォルト |---|---|---| | Tools | 5 | 8 (`task_create/update/list/get`) | | 計画モデル | フラットチェックリスト (メモリ) | 依存関係付きタスクグラフ (ディスク) | -| 関係 | なし | `blockedBy` + `blocks` エッジ | +| 関係 | なし | `blockedBy` エッジ | | ステータス追跡 | 完了か未完了 | `pending` -> `in_progress` -> `completed` | | 永続性 | 圧縮で消失 | 圧縮・再起動後も存続 | diff --git a/docs/zh/s07-task-system.md b/docs/zh/s07-task-system.md index 81ce309bb..3b53e776f 100644 --- a/docs/zh/s07-task-system.md +++ b/docs/zh/s07-task-system.md @@ -14,7 +14,7 @@ s03 的 TodoManager 只是内存中的扁平清单: 没有顺序、没有依赖 ## 解决方案 -把扁平清单升级为持久化到磁盘的**任务图**。每个任务是一个 JSON 文件, 有状态、前置依赖 (`blockedBy`) 和后置依赖 (`blocks`)。任务图随时回答三个问题: +把扁平清单升级为持久化到磁盘的**任务图**。每个任务是一个 JSON 文件, 有状态和前置依赖 (`blockedBy`)。任务图随时回答三个问题: - **什么可以做?** -- 状态为 `pending` 且 `blockedBy` 为空的任务。 - **什么被卡住?** -- 等待前置任务完成的任务。 @@ -59,8 +59,7 @@ class TaskManager: def create(self, subject, description=""): task = {"id": self._next_id, "subject": subject, - "status": "pending", "blockedBy": [], - "blocks": [], "owner": ""} + "status": "pending", "blockedBy": [], "owner": ""} self._save(task) self._next_id += 1 return json.dumps(task, indent=2) @@ -81,12 +80,16 @@ def _clear_dependency(self, completed_id): ```python def update(self, task_id, status=None, - add_blocked_by=None, add_blocks=None): + add_blocked_by=None, remove_blocked_by=None): task = self._load(task_id) if status: task["status"] = status if status == "completed": self._clear_dependency(task_id) + if add_blocked_by: + task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) + if remove_blocked_by: + task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] self._save(task) ``` @@ -110,7 +113,7 @@ TOOL_HANDLERS = { |---|---|---| | Tools | 5 | 8 (`task_create/update/list/get`) | | 规划模型 | 扁平清单 (仅内存) | 带依赖关系的任务图 (磁盘) | -| 关系 | 无 | `blockedBy` + `blocks` 边 | +| 关系 | 无 | `blockedBy` 边 | | 状态追踪 | 做完没做完 | `pending` -> `in_progress` -> `completed` | | 持久化 | 压缩后丢失 | 压缩和重启后存活 |