Learn-CC 学习笔记2

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(namedescription),存入 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 全量摘要

  1. 保存 transcript :完整对话写入 .transcripts/,JSONL 格式。transcript 保留了可恢复记录,但模型的活跃上下文里只剩摘要。对模型当下推理来说,细节已经不在上下文中了。教学代码没有提供 transcript 检索工具。
  2. LLM 生成摘要:把对话历史发给 LLM,要求保留当前目标、重要发现、已改文件、剩余工作、用户约束等关键信息。
  3. 替换消息列表:所有旧消息被替换为一条摘要。教学版只保留摘要;真实 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 列表
  1. 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}.jsonblockedBy 声明依赖,比如 "写 API" 的 blockedBy["task_schema"]

  1. 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_startclaim_task 的前置检查:blockedBy 里有任何一个不是 completed,就不能认领。不存在的依赖视为 blocked,避免引用错误 ID 时崩溃。

  1. 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})"
  1. 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
  1. 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 读文件就能恢复进度。

相关推荐
AI创飞人类1 小时前
企业 AI Agent 如何从 Demo 走向生产?知识库、权限、工具调用与版本治理
agent·智能体
水管在开花.1 小时前
RAG:知识库问答系统教学②-零基础保姆级教程
人工智能·面试·agent·rag
谢白羽1 小时前
SGLang模型加载过程笔记
笔记·llm·论文·agent·vllm·大模型部署·sglang
新知图书2 小时前
9.2 客户支持聊天机器人-项目架构设计
人工智能·agent·ai agent·智能体
xiezhr2 小时前
别把豆包当聊天用了,现在的豆包和以前不一样了
agent·ai编程·豆包marscode
路多辛2 小时前
为什么用 Go 写 AI Agent,covo-agent 的选型思考
开发语言·golang·agent·ai编程
xiezhr2 小时前
现在的豆包跟以前不一样了
人工智能·ai·agent·ai agent·豆包
魔术师Grace11 小时前
Agent总出错,什么时候才该训练模型?
llm·agent·强化学习
神奇霸王龙13 小时前
Cursor 3 + Claude Opus 4.8 屠榜:5 编程基座 IDE 卡位
ide·人工智能·ai·aigc·agent·ai编程·ai写作