Skip to content
Closed
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
7 changes: 5 additions & 2 deletions s10_task_system/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Task:
status: str # pending | in_progress | completed
owner: str | None # このタスクを担当する Agent
blockedBy: list[str] # 依存タスク ID のリスト
priority: int = 5 # 0-10、大きいほど先に実行
```

ID は `task_` と 8 桁のランダムな 16 進文字で生成する。ファイルは排他的に作成し、同じ ID が存在する場合は生成し直す。
Expand All @@ -69,12 +70,14 @@ ID は `task_` と 8 桁のランダムな 16 進文字で生成する。ファ
### create_task: タスク作成

```python
def create_task(subject: str, description: str = "") -> Task:
return TASKS.create(subject, description)
def create_task(subject: str, description: str = "", priority: int = 5) -> Task:
return TASKS.create(subject, description, priority)
```

`TaskStore.create` は subject を確認し、ランダム ID を割り当てて `.tasks/{id}.json` に書き込む。新しいタスクの `blockedBy` は常に空で、ツール結果が実行時に生成された ID をモデルへ返す。

`priority` は 0(最低)から 10(最高)までの整数で、デフォルトは 5 である。`create_task` はそれ以外の値を拒否するため、保存されたすべてのレコードを安全に比較できる。この章ではフィールドの記録と検証だけを行い、後続の章で複数の実行可能タスクのうちどれを先に実行するかを決めるために使う。

### update_task: 返された ID で依存を追加

```python
Expand Down
7 changes: 5 additions & 2 deletions s10_task_system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Task:
status: str # pending | in_progress | completed
owner: str | None # Agent responsible for this task
blockedBy: list[str] # List of dependency task IDs
priority: int = 5 # 0-10, higher runs first
```

IDs use the `task_` prefix followed by 8 random hexadecimal characters. Files are created exclusively; an existing ID is discarded and regenerated.
Expand All @@ -69,12 +70,14 @@ IDs use the `task_` prefix followed by 8 random hexadecimal characters. Files ar
### create_task: Create Tasks

```python
def create_task(subject: str, description: str = "") -> Task:
return TASKS.create(subject, description)
def create_task(subject: str, description: str = "", priority: int = 5) -> Task:
return TASKS.create(subject, description, priority)
```

`TaskStore.create` checks the subject, allocates a random ID, and writes `.tasks/{id}.json`. A new task always starts with an empty `blockedBy` list. The tool result returns the runtime-generated ID to the model.

A task's `priority` is an integer from 0 (lowest) to 10 (highest); the default is 5. `create_task` rejects anything else, so every stored record is safe to compare. This chapter only records and validates the field -- later chapters use it to decide which ready task runs first.

### update_task: Add Dependencies with Returned IDs

```python
Expand Down
7 changes: 5 additions & 2 deletions s10_task_system/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Task:
status: str # pending | in_progress | completed
owner: str | None # 负责当前任务的 Agent
blockedBy: list[str] # 依赖的任务 ID 列表
priority: int = 5 # 0-10,数值越高越先执行
```

ID 使用 `task_` 加 8 位随机十六进制字符生成。创建文件时使用排他写入;如果 ID 已存在,就重新生成。
Expand All @@ -69,12 +70,14 @@ ID 使用 `task_` 加 8 位随机十六进制字符生成。创建文件时使
### create_task: 创建任务

```python
def create_task(subject: str, description: str = "") -> Task:
return TASKS.create(subject, description)
def create_task(subject: str, description: str = "", priority: int = 5) -> Task:
return TASKS.create(subject, description, priority)
```

`TaskStore.create` 检查 subject,分配随机 ID,再把任务写入 `.tasks/{id}.json`。新任务的 `blockedBy` 固定为空,工具结果会把运行时生成的 ID 返回给模型。

任务的 `priority` 是 0(最低)到 10(最高)之间的整数,默认值是 5。`create_task` 会拒绝任何非法取值,因此存下来的每条记录都可以安全比较。本章只负责记录和校验该字段——后面的章节会用它在多个就绪任务中决定先执行哪一个。

### update_task: 使用返回的 ID 添加依赖

```python
Expand Down
34 changes: 25 additions & 9 deletions s10_task_system/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@
TASK_ID_PATTERN = re.compile(r"^task_[0-9a-f]{8}$")


def _validate_priority(priority: int) -> int:
"""Priority must be an integer between 0 (lowest) and 10 (highest)."""
if isinstance(priority, bool) or not isinstance(priority, int):
raise ValueError("priority must be an integer between 0 and 10")
if not 0 <= priority <= 10:
raise ValueError("priority must be an integer between 0 and 10")
return priority


@dataclass
class Task:
id: str
Expand All @@ -73,6 +82,7 @@ class Task:
status: str
owner: str | None
blockedBy: list[str]
priority: int = 5 # 0-10, higher runs first


class TaskStore:
Expand All @@ -99,10 +109,12 @@ def _path(self, task_id: str, create_root: bool = False) -> Path:
def exists(self, task_id: str) -> bool:
return self._path(task_id).is_file()

def create(self, subject: str, description: str = "") -> Task:
def create(self, subject: str, description: str = "",
priority: int = 5) -> Task:
subject = subject.strip()
if not subject:
raise ValueError("Task subject cannot be empty")
priority = _validate_priority(priority)

self._root(create=True)
for _ in range(100):
Expand All @@ -113,6 +125,7 @@ def create(self, subject: str, description: str = "") -> Task:
status="pending",
owner=None,
blockedBy=[],
priority=priority,
)
try:
with self._path(task.id, create_root=True).open(
Expand Down Expand Up @@ -183,6 +196,7 @@ def load(self, task_id: str) -> Task:
raise ValueError(f"Task file ID does not match {task_id}")
if task.status not in ("pending", "in_progress", "completed"):
raise ValueError(f"Invalid task status: {task.status}")
_validate_priority(task.priority)
return task

def list(self) -> list[Task]:
Expand All @@ -196,8 +210,9 @@ def list(self) -> list[Task]:
TASKS = TaskStore(TASKS_DIR)


def create_task(subject: str, description: str = "") -> Task:
return TASKS.create(subject, description)
def create_task(subject: str, description: str = "",
priority: int = 5) -> Task:
return TASKS.create(subject, description, priority)


def update_task(task_id: str, addBlockedBy: list[str]) -> Task:
Expand Down Expand Up @@ -338,10 +353,11 @@ def run_glob(pattern: str) -> str:
return f"Error: {error}"


def run_create_task(subject: str, description: str = "") -> str:
task = create_task(subject, description)
def run_create_task(subject: str, description: str = "",
priority: int = 5) -> str:
task = create_task(subject, description, priority)
print(f" [create] {task.subject}")
return f"Created {task.id}: {task.subject}"
return f"Created {task.id}: {task.subject} (p{task.priority})"


def run_update_task(task_id: str, addBlockedBy: list[str]) -> str:
Expand All @@ -368,7 +384,7 @@ def run_list_tasks() -> str:
)
owner = f" [{task.owner}]" if task.owner else ""
lines.append(
f"{marker} {task.id}: {task.subject} "
f"{marker} {task.id} (p{task.priority}): {task.subject} "
f"[{task.status}]{owner}{dependencies}"
)
return "\n".join(lines)
Expand Down Expand Up @@ -397,8 +413,8 @@ def run_complete_task(task_id: str) -> str:
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
{"name": "glob", "description": "Find files matching a glob pattern; ** matches recursively.",
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
{"name": "create_task", "description": "Create a task and return its runtime-generated ID.",
"input_schema": {"type": "object", "properties": {"subject": {"type": "string"}, "description": {"type": "string"}}, "required": ["subject"], "additionalProperties": False}},
{"name": "create_task", "description": "Create a task (priority 0-10, 5 default) and return its runtime-generated ID.",
"input_schema": {"type": "object", "properties": {"subject": {"type": "string"}, "description": {"type": "string"}, "priority": {"type": "integer", "minimum": 0, "maximum": 10}}, "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 tasks with status, owner, and dependencies.",
Expand Down
22 changes: 16 additions & 6 deletions s13_agent_teams/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,24 @@ shutdown、計画承認、Lead からの直接指示は、空き時間に見つ
走査は候補を探すだけで、状態を変更しない:

```python
def _ready_task_key(task: Task) -> tuple[int, str]:
"""Deterministic order: highest priority first, then smallest task_id."""
return (-task.priority, task.id)

def scan_unclaimed_tasks() -> list[Task]:
return [
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
]
return sorted(
[
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
],
key=_ready_task_key,
)
```

複数の候補が同時に ready になった時は `priority` が順番を決める。各タスクは 0(最低)から 10(最高)、デフォルト 5 の `priority` を持つ。ready なタスクは priority 順に実行する——高い方が先で、同じ priority 同士は `task.id` の昇順で決めるため、順序は決定的である。同じ task directory を見るどのチームメイトも同じ「次のタスク」を目にし、`claim_next_task` は常に並べ替えたリストの先頭を試みる。

候補一覧は一時点の snapshot にすぎない。別のチームメイトだけでなく、同じ task directory を使う別の Harness process も同じ task を見る可能性がある。そのため、所有権の変更は process 内 lock と file lock を組み合わせた `task_store_lock()` の下で `claim_task()` が行う:

```python
Expand Down Expand Up @@ -262,6 +271,7 @@ class Task:
owner: str | None
blockedBy: list[str]
worktree: str | None = None
priority: int = 5 # 0-10、大きいほど先に実行
```

並列編集を別ディレクトリに分けたい時、Lead は worktree を作成してタスクへ紐付けられる:
Expand Down
22 changes: 16 additions & 6 deletions s13_agent_teams/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,24 @@ Shutdown, plan approval, and direct instructions from Lead should arrive before
Scanning only finds candidates:

```python
def _ready_task_key(task: Task) -> tuple[int, str]:
"""Deterministic order: highest priority first, then smallest task_id."""
return (-task.priority, task.id)

def scan_unclaimed_tasks() -> list[Task]:
return [
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
]
return sorted(
[
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
],
key=_ready_task_key,
)
```

When several candidates are ready at once, `priority` decides. Each task carries a `priority` from 0 (lowest) to 10 (highest), default 5. Ready tasks run in priority order -- highest first; equal priorities are broken by `task.id` ascending, so the ordering is deterministic. Every teammate inspecting the same task directory sees the same next task, and `claim_next_task` always tries the first entry in the ordered list.

The list is a snapshot. Another teammate, or another harness process using the same task directory, may see the same task. Ownership changes therefore happen inside `claim_task()` under `task_store_lock()`, which combines the in-process lock with a file lock:

```python
Expand Down Expand Up @@ -262,6 +271,7 @@ class Task:
owner: str | None
blockedBy: list[str]
worktree: str | None = None
priority: int = 5 # 0-10, higher runs first
```

Lead can create and bind a worktree when separate directories will help:
Expand Down
22 changes: 16 additions & 6 deletions s13_agent_teams/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,24 @@ while True:
扫描只负责找候选任务:

```python
def _ready_task_key(task: Task) -> tuple[int, str]:
"""Deterministic order: highest priority first, then smallest task_id."""
return (-task.priority, task.id)

def scan_unclaimed_tasks() -> list[Task]:
return [
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
]
return sorted(
[
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
],
key=_ready_task_key,
)
```

当多个候选同时就绪时,由 `priority` 决定先后。每个任务带有一个 `priority`,取值 0(最低)到 10(最高),默认 5。就绪任务按 priority 排序——数值高的先执行;优先级相同时按 `task.id` 升序打破平局,因此顺序是确定性的。任何队友查看同一任务目录,看到的都是同一个"下一个任务",`claim_next_task` 总是先尝试有序列表的第一项。

候选列表只是某一时刻的快照。其他队友,甚至另一个使用同一任务目录的 Harness 进程,也可能看到同一任务。因此所有权变更必须放进 `claim_task()`,并由 `task_store_lock()` 同时取得进程内锁和文件锁:

```python
Expand Down Expand Up @@ -261,6 +270,7 @@ class Task:
owner: str | None
blockedBy: list[str]
worktree: str | None = None
priority: int = 5 # 0-10,数值越高越先执行
```

并行修改需要分开目录时,Lead 可以创建并绑定 worktree:
Expand Down
Loading