Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions agents/s07_task_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"):
Expand All @@ -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)

Expand Down Expand Up @@ -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"]),
}
Expand All @@ -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.",
Expand Down
13 changes: 8 additions & 5 deletions docs/en/s07-task-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
```

Expand All @@ -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 |

Expand Down
13 changes: 8 additions & 5 deletions docs/ja/s07-task-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ s03のTodoManagerはメモリ上のフラットなチェックリストに過ぎ

## 解決策

フラットなチェックリストをディスクに永続化する**タスクグラフ**に昇格させる。各タスクは1つのJSONファイルで、ステータス・前方依存(`blockedBy`)・後方依存(`blocks`)を持つ。タスクグラフは常に3つの問いに答える:
フラットなチェックリストをディスクに永続化する**タスクグラフ**に昇格させる。各タスクは1つのJSONファイルで、ステータスと前方依存(`blockedBy`)を持つ。タスクグラフは常に3つの問いに答える:

- **何が実行可能か?** -- `pending`ステータスで`blockedBy`が空のタスク。
- **何がブロックされているか?** -- 未完了の依存を待つタスク。
Expand Down Expand Up @@ -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)
Expand All @@ -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)
```

Expand All @@ -110,7 +113,7 @@ s07以降、タスクグラフがマルチステップ作業のデフォルト
|---|---|---|
| Tools | 5 | 8 (`task_create/update/list/get`) |
| 計画モデル | フラットチェックリスト (メモリ) | 依存関係付きタスクグラフ (ディスク) |
| 関係 | なし | `blockedBy` + `blocks` エッジ |
| 関係 | なし | `blockedBy` エッジ |
| ステータス追跡 | 完了か未完了 | `pending` -> `in_progress` -> `completed` |
| 永続性 | 圧縮で消失 | 圧縮・再起動後も存続 |

Expand Down
13 changes: 8 additions & 5 deletions docs/zh/s07-task-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ s03 的 TodoManager 只是内存中的扁平清单: 没有顺序、没有依赖

## 解决方案

把扁平清单升级为持久化到磁盘的**任务图**。每个任务是一个 JSON 文件, 有状态、前置依赖 (`blockedBy`) 和后置依赖 (`blocks`)。任务图随时回答三个问题:
把扁平清单升级为持久化到磁盘的**任务图**。每个任务是一个 JSON 文件, 有状态和前置依赖 (`blockedBy`)。任务图随时回答三个问题:

- **什么可以做?** -- 状态为 `pending` 且 `blockedBy` 为空的任务。
- **什么被卡住?** -- 等待前置任务完成的任务。
Expand Down Expand Up @@ -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)
Expand All @@ -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)
```

Expand All @@ -110,7 +113,7 @@ TOOL_HANDLERS = {
|---|---|---|
| Tools | 5 | 8 (`task_create/update/list/get`) |
| 规划模型 | 扁平清单 (仅内存) | 带依赖关系的任务图 (磁盘) |
| 关系 | 无 | `blockedBy` + `blocks` 边 |
| 关系 | 无 | `blockedBy` 边 |
| 状态追踪 | 做完没做完 | `pending` -> `in_progress` -> `completed` |
| 持久化 | 压缩后丢失 | 压缩和重启后存活 |

Expand Down