s06 Subagent
大任务拆小,每个拿到的都是干净上下文
Harness 层: 子 Agent --- 上下文隔离, 注意力不漂移。
| 模式 | 触发 | 恢复动作 |
|---|---|---|
| 输出截断 | max_tokens |
升级 8K→64K / 续写提示 |
| 上下文超限 | prompt_too_long |
reactive compact → 重试 |
| 临时故障 | 429 / 529 | 指数退避 + 抖动,连续 529 可切换备用模型 |
python
def spawn_subagent(description: str) -> str:
# 子 Agent 的工具:基础工具,但没有 task(禁止递归)也就是子Agent不能再有自己的子Agent
sub_tools = [
{"name": "bash", ...}, {"name": "read_file", ...},
{"name": "write_file", ...}, {"name": "edit_file", ...},
{"name": "glob", ...},
]
# 与父 Agent 没有关联的消息列表
messages = [{"role": "user", "content": description}] # 全新 messages[]
for _ in range(30): # safety limit 最大循环次数限制
response = client.messages.create(
model=MODEL, system=SUB_SYSTEM,
messages=messages, tools=sub_tools, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break
results = []
for block in response.content:
if block.type == "tool_use":
blocked = trigger_hooks("PreToolUse", block)
if blocked:
results.append({... "content": str(blocked)})
continue
handler = SUB_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown"
trigger_hooks("PostToolUse", block, output)
results.append({... "content": output})
messages.append({"role": "user", "content": results})
# 只返回最后的文本结论,中间过程全部丢弃
return extract_text(messages[-1]["content"])
主 Agent 就像调用其他工具一样
ini
TOOLS = [
{"name": "bash", ...},
{"name": "read_file", ...},
{"name": "write_file", ...},
{"name": "edit_file", ...},
{"name": "glob", ...},
{"name": "todo_write", ...},
# s06: 新增 task 工具
{"name": "task",
"description": "Launch a subagent to handle a complex subtask. Returns only the final conclusion.",
"input_schema": {"type": "object", "properties": {"description": {"type": "string"}}, "required": ["description"]}},
]
TOOL_HANDLERS["task"] = spawn_subagent
s07 Skill Loading
用到的时候才加载
Harness 层: 知识 --- 按需加载, 不堆满上下文
章重点转向新增的 load_skill 工具。启动时把技能目录注入 SYSTEM prompt,运行时多注册一个工具加载完整内容,用到才花 token。
objectivec
skills/
agent-builder/SKILL.md
code-review/SKILL.md
mcp-builder/SKILL.md
pdf/SKILL.md
第一级:启动时注入目录 :harness 启动时调用 _scan_skills() 扫描 skills/ 目录,解析每个 SKILL.md 的 YAML frontmatter(name、description),存入 SKILL_REGISTRY 字典。list_skills() 从注册表生成目录,注入 SYSTEM prompt。Agent 每轮都能看到"我有哪些技能可用",不花额外 API 调用:
scss
SKILL_REGISTRY: dict[str, dict] = {}
def _scan_skills():
if not SKILLS_DIR.exists():
return
for d in sorted(SKILLS_DIR.iterdir()):
if not d.is_dir():
continue
manifest = d / "SKILL.md"
if manifest.exists():
raw = manifest.read_text()
meta, body = _parse_frontmatter(raw)
name = meta.get("name", d.name)
desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}
_scan_skills() # runs once at startup
def list_skills() -> str:
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
def build_system() -> str:
catalog = list_skills()
return (
f"You are a coding agent at {WORKDIR}. "
f"Skills available:\n{catalog}\n"
"Use load_skill to get full details when needed."
)
SYSTEM = build_system()
第二级:load_skill :Agent 决定"我需要 SQL 风格指南",调用 load_skill("sql-style")。通过注册表查找,不走文件路径,没有路径遍历风险。SKILL.md 内容通过 tool_result 注入,并可通过现有的 file 和 bash 工具进一步访问引用的 references/、scripts/ 或 assets/。
python
def load_skill(name: str) -> str:
skill = SKILL_REGISTRY.get(name)
if not skill:
return f"Skill not found: {name}"
return skill["content"]
我的理解:有一个 skill 目录,系统先扫描目录,生成一个 skill 注册表,然后读取注册表生成一个目录文本字符串,把这个字符串放入 system 提示词中让 Agent 按需加载
s08 Context Compact
上下文总会满,要有办法腾地方
Harness 层: 压缩 --- 干净的记忆, 无限的会话。
上下文窗口是有限的。满了之后,API 直接拒绝:prompt_too_long。
L1: snip_compact --- 裁掉无关的旧对话
ini
def snip_compact(messages, max_messages=50):
# 简单裁剪,留下头部3条信息,裁剪掉中间,拼接结尾47条,共50条消息
if len(messages) <= max_messages:
return messages
head_end, tail_start = 3, len(messages) - (max_messages - 3)
# 修正1:当头部的第3条消息是工具调用,就向右移,跨过全部工具结果消息,把此次工具对话全包进来
if head_end > 0 and _message_has_tool_use(messages[head_end - 1]):
while head_end < len(messages) and _is_tool_result_message(messages[head_end]):
head_end += 1
# 修正2:当尾部开始的那一条消息是工具结果,并且尾部开始之前的一条消息是工具调用,就把tail_start向左移
if (tail_start > 0 and tail_start < len(messages)
and _is_tool_result_message(messages[tail_start])
and _message_has_tool_use(messages[tail_start - 1])):
tail_start -= 1
snipped = tail_start - head_end
# 把头部消息、中间裁剪信息和尾部消息拼接
placeholder = {"role": "user", "content": f"[snipped {snipped} messages from conversation middle]"}
return messages[:head_end] + [placeholder] + messages[tail_start:]
裁掉的是消息本身,只是在切口处多做一步保护;剩下的消息里 tool_result 内容仍在累积------第 34 条消息里可能躺着 30KB 的旧文件内容。→ L2。
L2: micro_compact --- 旧工具结果占位
python
# 最近三组工具调用
KEEP_RECENT_TOOL_RESULTS = 3
def micro_compact(messages):
# 遍历 message 收集工具调用块
tool_results = collect_tool_result_blocks(messages)
if len(tool_results) <= KEEP_RECENT_TOOL_RESULTS:
return messages
# 取除了最后3组的其他工具块,如果长度过长就压缩
for _, _, block in tool_results[:-KEEP_RECENT_TOOL_RESULTS]:
if len(block.get("content", "")) > 120:
block["content"] = "[Earlier tool result compacted. Re-run if needed.]"
return messages
旧结果清掉了,但单条新结果可能就有 500KB------一个 cat 大文件的输出就能打满上下文。→ L3。
L3: tool_result_budget --- 大结果落盘
模型一次读了 5 个大文件,单条 user 消息里所有 tool_result 加起来 500KB。
统计最后一条 user 消息里所有 tool_result 的总大小。超过 200KB → 按大小排序,从最大的开始落盘到 .task_outputs/tool-results/,上下文里只留 <persisted-output> 标记 + 前 2000 字符预览。模型看到标记后知道完整内容在磁盘上,需要时可以重新读。
python
def tool_result_budget(messages, max_bytes=200_000):
# message的最后一条
last = messages[-1]
# 拿到对话列表**最新的一条消息**,通常是本轮刚刚生成、包含一批`tool_result`的 user 消息。
blocks = [(i, b) for i, b in enumerate(last["content"])
if b.get("type") == "tool_result"]
# 统计:当前这批全部 tool‑result 内容加起来的**总字符字节长度**
total = sum(len(str(b.get("content", ""))) for _, b in blocks)
if total <= max_bytes:
return messages
# 先排个序
ranked = sorted(blocks, key=lambda p: len(str(p[1].get("content", ""))), reverse=True)
"""
循环: 只要整体总大小仍然超上限:
1. 取出当前最大的工具结果块
2. 调用persist_large_output把超长文本存到外部
返回一条简短占位字符串,替换掉原来超长content
3. 重新计算现在全部块总长度recalculate_total
4. 如果已经压到限额以内 total <= max_bytes,立刻 break 停止压缩
"""
for idx, block in ranked:
if total <= max_bytes:
break
block["content"] = persist_large_output(block["tool_use_id"], str(block["content"]))
total = recalculate_total(blocks)
return messages
前三层都是纯文本/结构操作,0 API 调用,但也无法"理解"对话内容。上下文可能仍然太大。→ L4。
L4: compact_history --- LLM 全量摘要
- 保存 transcript :完整对话写入
.transcripts/,JSONL 格式。transcript 保留了可恢复记录,但模型的活跃上下文里只剩摘要。对模型当下推理来说,细节已经不在上下文中了。教学代码没有提供 transcript 检索工具。 - LLM 生成摘要:把对话历史发给 LLM,要求保留当前目标、重要发现、已改文件、剩余工作、用户约束等关键信息。
- 替换消息列表:所有旧消息被替换为一条摘要。教学版只保留摘要;真实 Claude Code 会在 compact 后重新附加部分最近文件、计划、agent/skill/tool 等上下文。
python
def compact_history(messages):
transcript_path = write_transcript(messages) # 先保存完整对话
summary = summarize_history(messages) # LLM 生成摘要
return [{"role": "user",
"content": f"[Compacted]\n\n{summary}"}]
熔断器:连续失败 3 次后停止重试,防止死循环浪费 API 调用。
应急: reactive_compact
有时候 API 还是返回 prompt_too_long(413),上下文增长速度快于压缩触发速度时。
这时触发 reactive_compact :触发方式比 compact_history 更激进(API 报错后的应急手段),但压缩策略更温和,保留最近约 5 条原始消息,只总结较早历史。同样避免留下孤立 tool_result。
css
def reactive_compact(messages):
transcript = write_transcript(messages)
tail_start = max(0, len(messages) - 5)
if (tail_start > 0 and tail_start < len(messages)
and _is_tool_result_message(messages[tail_start])
and _message_has_tool_use(messages[tail_start - 1])):
tail_start -= 1
summary = summarize_history(messages[:tail_start])
return [{"role": "user",
"content": f"[Reactive compact]\n\n{summary}"}, *messages[tail_start:]]
合起来
python
def agent_loop(messages):
reactive_retries = 0
while True:
# 三个预处理器(0 API 调用)
# 顺序:budget 先跑,确保大内容落盘后再做占位和裁剪
messages[:] = tool_result_budget(messages) # L3: 大结果落盘
messages[:] = snip_compact(messages) # L1: 裁中间
messages[:] = micro_compact(messages) # L2: 旧结果占位
# 还不够?LLM 摘要(1 API 调用)
if estimate_token_count(messages) > THRESHOLD:
messages[:] = compact_history(messages)
try:
response = client.messages.create(...)
except PromptTooLongError:
if reactive_retries < MAX_REACTIVE_RETRIES:
messages[:] = reactive_compact(messages) # 应急
reactive_retries += 1
continue
raise # 超过重试上限,抛出异常
# ... 工具执行 ...
# compact 工具:模型主动调用时触发 compact_history
if block.name == "compact":
messages[:] = compact_history(messages)
results.append({..., "content": "[Compacted. History summarized.]"})
messages.append({"role": "user", "content": results})
break # 结束当前 turn,用压缩后的上下文开始新一轮
s09 Memory
压缩会丢细节,要有一层不丢的------文件仓库 + 索引 + 按需加载,跨压缩、跨会话。
Harness 层: 记忆 --- 跨压缩、跨会话的知识积累。
LLM 没有持久状态,所有信息都在上下文窗口里。上下文满了要压缩,压缩就有损。需要一层不参与压缩、跨会话保留的存储。

存储:Markdown 文件 + 索引
每个记忆是一个 .md 文件,YAML frontmatter 记录元数据:
yaml
---
name: user-preference-tabs
description: User prefers tabs for indentation
type: user
---
User prefers using tabs, not spaces, for indentation.
**Why:** Consistency with existing codebase conventions.
**How to apply:** Always use tabs when writing or editing files.
//用户缩进偏好 Tab 制表符,不要空格
MEMORY.md 是索引,一行一个链接:
sql
- [user-preference-tabs](user-preference-tabs.md) --- User prefers tabs for indentation
写入新记忆时自动重建索引:
python
def write_memory_file(name, mem_type, description, body):
# 名称全小写,空格替换为-
slug = name.lower().replace(" ", "-")
# 写入为带 Front‑Matter 的 md 文件
filepath = MEMORY_DIR / f"{slug}.md"
filepath.write_text(
f"---\nname: {name}\ndescription: {description}\ntype: {mem_type}\n---\n\n{body}\n"
)
# 重建索引,写完文件之后,立刻重新扫描一遍`MEMORY_DIR`,刷新内存里面记忆注册表。
_rebuild_index()
加载:两条路径
路径一:索引常驻 SYSTEM。 build_system() 在每次用户请求开始时读取 MEMORY.md,把记忆清单注入。记忆提取和整理只在本轮结束时触发,因此同一轮用户请求中不需要重复重建 SYSTEM。
路径二:相关记忆按需注入。 每次用户请求开始时,load_memories() 把最近对话和记忆目录(name + description)一起发给 LLM 做一次轻量 side-query,选出相关的文件名,再读文件内容临时注入到当前 user turn。最多 5 条,控制开销。
python
# 搜索相关记忆
def select_relevant_memories(messages, max_items=5):
files = list_memory_files()
if not files:
return []
# 目录 Build catalog: "0: user-preference-tabs --- User prefers tabs..."
catalog = "\n".join(f"{i}: {f['name']} --- {f['description']}" for i, f in enumerate(files))
# 调用 LLM 搜索相关记忆
response = client.messages.create(model=MODEL, messages=[{"role": "user",
"content": f"Select relevant memory indices. Return JSON array.\n\n"
f"Recent conversation:\n{recent}\n\nMemory catalog:\n{catalog}"}],
max_tokens=200)
text = extract_text(response.content).strip()
indices = json.loads(re.search(r'[.*?]', text).group())
return [files[i]["filename"] for i in indices if 0 <= i < len(files)]
写入:每轮结束后提取
extract_memories() 在每轮结束时运行,条件是模型停止且没有 tool_use(说明对话告一段落):
python
# In agent_loop:
if response.stop_reason != "tool_use":
extract_memories(pre_compress) # 从压缩前快照提取新记忆
consolidate_memories() # 检查是否需要整理
return
def extract_memories(messages):
dialogue = format_recent_messages(messages[-10:])
existing = "\n".join(f"- {m['name']}: {m['description']}" for m in list_memory_files())
prompt = (
"Extract user preferences, constraints, or project facts.\n"
"Return JSON array: [{name, type, description, body}].\n"
"If nothing new or already covered, return [].\n\n"
f"Existing memories:\n{existing}\n\nDialogue:\n{dialogue[:4000]}"
)
# ... parse response, write files ...
整理:低频合并去重
记忆文件会积累。consolidate_memories() 在文件数达到阈值(默认 10)时触发,让 LLM 去重、合并矛盾、淘汰过时记忆:
ini
CONSOLIDATE_THRESHOLD = 10
def consolidate_memories():
files = list_memory_files()
if len(files) < CONSOLIDATE_THRESHOLD:
return # 太少,不值得整理
# Send all memories to LLM, get back deduplicated list
# Replace all files with consolidated results
s10 System Prompt
运行时组装,不硬编码
Harness 层: 提示 --- 运行时组装, 不硬编码。
把硬编码的 SYSTEM 拆成独立段落(section),运行时根据真实状态按需拼接,缓存结果避免重复组装。
四个 section,两种加载策略:
| Section | 加载策略 | 内容 | 判断依据 |
|---|---|---|---|
| identity | 始终 | 你是谁、怎么做事 | 始终存在 |
| tools | 始终 | 可用工具列表 | enabled_tools |
| workspace | 始终 | 工作目录 | 始终存在 |
| memory | 按需 | 相关记忆内容 | .memory/MEMORY.md 是否存在 |
PROMPT_SECTION 分段定义
把一大段字符串拆成字典,每个 key 是一个主题:
makefile
PROMPT_SECTIONS = {
"identity": "You are a coding agent. Act, don't explain.",
}
按需拼接提示词
python
def assemble_system_prompt(context: dict) -> str:
sections = []
# 始终加载
sections.append(PROMPT_SECTIONS["identity"])
# 从 context 动态获取 tools 和 workspace
tools = ", ".join(context.get("enabled_tools", []))
if tools:
sections.append(f"Available tools: {tools}.")
sections.append(f"Working directory: {context.get("workspace", WORKDIR)}")
# 按需加载 --- 基于真实状态,不是关键词
memories = context.get("memories", "")
if memories:
sections.append(f"Relevant memories:\n{memories}")
return "\n\n".join(sections)
get_system_prompt: 缓存避免重复拼接
上下文没变时(同一轮对话的多次 LLM 调用,context 相同),重新拼接是浪费。用确定性序列化检测变化,命中缓存直接返回:
ini
def get_system_prompt(context: dict) -> str:
global _last_context_key, _last_prompt
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
if key == _last_context_key and _last_prompt:
return _last_prompt
_last_context_key = key
_last_prompt = assemble_system_prompt(context)
return _last_prompt
context: 真实状态,不是关键词猜测
context 反映当前运行态的真实状态:
css
def update_context(context: dict, messages: list) -> dict:
memories = ""
if MEMORY_INDEX.exists():
content = MEMORY_INDEX.read_text().strip()
if content:
memories = content
return {
"enabled_tools": list(TOOL_HANDLERS.keys()),
"workspace": str(WORKDIR),
"memories": memories,
}
合起来跑
ini
def agent_loop(messages: list, context: dict):
system = get_system_prompt(context)
while True:
response = client.messages.create(
model=MODEL, system=system, messages=messages,
tools=TOOLS, max_tokens=8000)
# ... 工具执行 ...
context = update_context(context, messages)
system = get_system_prompt(context)
s11 Error Recovery
错误不是结束,是重试的开始
Harness 层: 韧性 --- 主循环遇到错误时分类并恢复。
生产环境中 API 错误是常态。三种最常见的故障模式:输出被截断 (模型话说一半 token 用完了)、上下文超限 (压缩后还是太长)、临时故障(429 限流 / 529 过载)。一个不处理错误的 Agent 就像一个一碰就熄火的车。
s10 的循环、prompt 组装全部保留。唯一的变动:LLM 调用包裹在 try/except 里,根据错误类型走不同的恢复路径。恢复后 continue 回到循环开头重新调用 LLM。
| 模式 | 触发 | 恢复动作 |
|---|---|---|
| 输出截断 | max_tokens |
升级 8K→64K / 续写提示 |
| 上下文超限 | prompt_too_long |
reactive compact → 重试 |
| 临时故障 | 429 / 529 | 指数退避 + 抖动,连续 529 可切换备用模型 |
路径 1: 输出被截断
模型话说一半,max_tokens 用完了。默认 8000 token 不够它输出完整回答。
第一次发生时,直接把 max_tokens 从 8K 升级到 64K(8 倍空间),重试同一请求------此时不追加截断输出到 messages,保持原始请求不变。如果 64K 还是不够,才保存截断输出并注入续写提示让模型接着刚才的话继续说,最多 3 次:
python
if response.stop_reason == "max_tokens":
# First escalation: don't append truncated output, retry same request
if not state.has_escalated:
max_tokens = ESCALATED_MAX_TOKENS
state.has_escalated = True
continue # messages unchanged, same request with more tokens
# 64K still truncated: save output + continuation prompt
messages.append({"role": "assistant", "content": response.content})
if state.recovery_count < MAX_RECOVERY_RETRIES:
messages.append({"role": "user", "content":
"Output token limit hit. Resume directly --- "
"no apology, no recap. Pick up mid-thought."})
state.recovery_count += 1
continue
return # still truncated after 3 continuations
# Normal: append after max_tokens check
messages.append({"role": "assistant", "content": response.content})
路径 2: 上下文超限
LLM 说"你的上下文太长了"(prompt_too_long)。s08 的四层压缩全跑过了,还是超。
真实实现会调用 LLM 生成 compact 摘要再重试。压缩后重试。
ini
except PromptTooLongError:
if not state.has_attempted_reactive_compact:
messages[:] = reactive_compact(messages)
state.has_attempted_reactive_compact = True
continue
return # 压缩过了还是超限,只能退出
路径 3: 临时故障
网络抖动、429 限流、529 过载------这些不是 bug,是分布式系统的常态。
429 和 529 统一走指数退避 + 抖动:第一次等 0.5 秒,第二次等 1 秒,第三次等 2 秒,最多 10 次。加随机抖动让并发请求不在同一时刻重试。连续 3 次 529 过载 → 切换到备用模型(若配置了 FALLBACK_MODEL_ID 环境变量):
python
def retry_delay(attempt, retry_after=None):
if retry_after:
return retry_after
base = min(500 * (2 ** attempt), 32000) / 1000
return base + random.uniform(0, base * 0.25)
def with_retry(fn, state, max_retries=10):
for attempt in range(max_retries):
try:
return fn()
except (RateLimitError, OverloadedError):
delay = retry_delay(attempt)
time.sleep(delay)
if is_overloaded:
state.consecutive_529 += 1
if state.consecutive_529 >= 3 and FALLBACK_MODEL:
state.current_model = FALLBACK_MODEL
raise MaxRetriesExceeded()
s12 Task System
Harness 层: 任务 --- 持久化的目标, 可恢复的进度。
问题:Agent 接到一个项目:搭数据库、写 API、加测试。它用 s05 的 TodoWrite 列了一张清单,然后开始写 API,写到一半发现没数据库表,回头补;加测试时发现 API 接口签名又变了...
s05 的 TodoWrite 是当前任务的执行清单,保存在会话内存中。这里需要的是任务系统 :每个任务是一个 JSON 文件,任务之间有 blockedBy 依赖,跨会话持久化在磁盘上。
TodoWrite vs Task System:
| TodoWrite (s05) | Task System (s12) | |
|---|---|---|
| 定位 | 当前任务的执行清单 | 可恢复的任务系统 |
| 存储 | 进程内 / 会话状态 | .tasks/{id}.json |
| 依赖 | 无 | blockedBy / blocks 依赖图 |
| 生命周期 | 当前会话 / 当前任务 | 跨会话保留 |
| 分工 | 不负责任务认领 | owner / claim |
| 状态 | pending / in_progress / completed | pending / in_progress / completed |
| 粒度 | Agent 自己的步骤 | 可被认领、追踪、解锁的任务 |
每个任务是一个 JSON 文件,存于 .tasks/ 目录:
python
@dataclass
class Task:
id: str
subject: str
description: str
status: str # pending | in_progress | completed
owner: str | None # Agent 名(多 Agent 场景)
blockedBy: list[str] # 依赖的任务 ID 列表
- create_task 创建任务
ini
def create_task(subject: str, description: str = "",
blockedBy: list[str] | None = None) -> Task:
task = Task(
id=f"task_{int(time.time())}_{random_hex(4)}",
subject=subject, description=description,
status="pending", owner=None,
blockedBy=blockedBy or [],
)
save_task(task)
return task
创建时自动 save_task 到 .tasks/{id}.json。blockedBy 声明依赖,比如 "写 API" 的 blockedBy 是 ["task_schema"]。
- can_task 依赖检查
python
def can_start(task_id: str) -> bool:
task = load_task(task_id)
for dep_id in task.blockedBy:
if not _task_path(dep_id).exists():
return False # missing dependency = blocked
dep = load_task(dep_id)
if dep.status != "completed":
return False
return True
can_start 是 claim_task 的前置检查:blockedBy 里有任何一个不是 completed,就不能认领。不存在的依赖视为 blocked,避免引用错误 ID 时崩溃。
- claim_task: 认领任务
python
def claim_task(task_id: str, owner: str = "agent") -> str:
task = load_task(task_id)
if task.status != "pending":
return f"Task {task_id} is {task.status}, cannot claim"
if not can_start(task_id):
deps = [d for d in task.blockedBy
if load_task(d).status != "completed"]
return f"Blocked by: {deps}"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task_id} ({task.subject})"
- complete_task: 完成与解锁
任务做完后,设为 completed。同时扫描所有其他任务,找出刚刚被解锁的下游任务:
python
def complete_task(task_id: str) -> str:
task = load_task(task_id)
task.status = "completed"
save_task(task)
# 找出被解锁的下游任务
unblocked = [t.subject for t in list_tasks()
if t.status == "pending" and t.blockedBy
and can_start(t.id)]
msg = f"Completed {task_id} ({task.subject})"
if unblocked:
msg += f"\nUnblocked: {', '.join(unblocked)}"
return msg
- get_task: 查看完整细节
python
def get_task(task_id: str) -> str:
task = load_task(task_id)
return json.dumps(asdict(task), indent=2)
合起来跑
scss
# 创建有依赖的任务
schema = create_task("setup database schema")
endpoints = create_task("create API endpoints", blockedBy=[schema.id])
tests = create_task("write tests", blockedBy=[endpoints.id])
docs = create_task("write docs", blockedBy=[schema.id])
# Agent 认领第一个可做的任务
claim_task(schema.id) # ✓ Claimed (无依赖)
complete_task(schema.id) # ✓ Completed → 解锁 endpoints, docs
claim_task(endpoints.id) # ✓ Claimed (schema 已完成)
complete_task(endpoints.id) # ✓ Completed → 解锁 tests
claim_task(docs.id) # ✓ Claimed (schema 已完成)
complete_task(docs.id) # ✓ Completed
claim_task(tests.id) # ✓ Claimed (endpoints 已完成)
complete_task(tests.id) # ✓ Completed
每个 create_task 写一个 JSON 文件,每个 claim_task / complete_task 更新文件。跨会话时,.tasks/ 目录还在,Agent 读文件就能恢复进度。