首页
/ learn-claude-code s13:Agent Teams 团队运行时——持久队友、原子任务认领、worktree 隔离与类型化协作协议

learn-claude-code s13:Agent Teams 团队运行时——持久队友、原子任务认领、worktree 隔离与类型化协作协议

2026-09-06 13:10:50作者:柏廷章Berta

本篇基于 learn-claude-code 课程的 s13 章节(s13_agent_teams/README.zh.md)与配套实现 s13_agent_teams/code.py,系统讲解多 Agent 协作的团队运行时:Lead 如何提出团队方案并等待用户确认、持久队友如何在线程中执行 WORK/IDLE 循环、MessageBus 文件收件箱如何把通信移出模型上下文、空闲队友如何原子认领共享任务板上的 ready task、任务如何绑定 Git worktree 隔离工作目录,以及关机与计划审批如何通过带 request_id 的类型化协议可追踪地执行。读完后你将能够理解并复刻一套"多个 Agent 并行分工、共享状态、接受 Lead 控制"的完整 harness 层设计。

Agent Teams 总览:Lead 管理持久队友,MessageBus 收件箱传递事件,共享任务板支持原子认领

一、问题:一个 Agent 装不下整项工作

假设让 Agent 重构整个后端,工作涉及配置加载、认证和测试。单个 Agent 可以依次处理,但总耗时更长,早期细节也会逐渐离开上下文。这类工作适合并行,而用户通常只描述目标,不会替运行时设计团队:

重构这个示例后端。清理配置加载、认证和测试,
保持现有接口,并确保测试通过。

Harness 需要回答一组相互关联的问题:

  1. 谁判断并行是否有用,新增 Agent 又由谁确认?
  2. 每个队友如何跨任务保留身份和上下文?
  3. 结果如何自动返回 Lead,而不是让模型轮询收件箱?
  4. 空闲队友能否直接接手 ready task,不再等待 Lead 逐项派发?
  5. 并行修改可能冲突时,任务应该使用哪个工作目录?
  6. 关机和计划审批如何成为可追踪、可执行的协议?

s13 复用 s10 任务系统的基础工具、Hooks、Permission 和 Task System,在此之上增加一套由 Lead 管理的团队运行时:

  • Lead 负责用户对话,提出分工方案并等待确认;
  • 队友 运行独立 Agent Loop,在 WORK 和 IDLE 之间切换;
  • MessageBus 通过文件收件箱传递普通消息、结果和控制事件;
  • 运行时投递 消费 Lead 的收件箱,把团队事件注入下一轮对话;
  • 共享任务板 让空闲队友发现 ready task,并在锁内完成认领;
  • 可选 worktree 在需要时把任务绑定到另一个工作目录,未绑定任务仍使用仓库目录;
  • 类型化协议和计划闸门 显式记录关机与审批状态,并在计划获批前阻止修改型工具。

需要明确:s11 的后台任务和 s12 的定时任务没有被带入本章,它们不参与队友通信、任务认领或计划审批。所有这些机制都属于 Team 这一层——任务发现不需要另一套 Agent Loop,worktree 也不会产生另一种 Agent。

二、Lead 先提出团队,再等待用户确认

启动队友会改变成本、并发度和可以修改工作区的角色集合。在 s13_agent_teams/code.py 中,Lead 的系统提示词把这条边界明确写进了 PROMPT_SECTIONS["teams"]

"When parallel work would help, first propose a small team with clear "
"responsibilities and wait for the user's confirmation. Do not call "
"spawn_teammate before the user confirms. After confirmation, delegate "
"independent work by creating a Task for each parallel change. ..."

收到第一条需求后,Lead 只提出分工,例如:

我建议并行处理三个方向:
- config:清理配置加载
- auth:重构认证
- tests:补充回归测试

你确认后我再启动队友。

用户回复"开始吧"后,Lead 才能调用 spawn_teammate。流程是:Lead 先创建任务,再把初始 task_id 传给队友。用户给出目标,Lead 设计团队,用户确认执行边界。

三、持久队友:与 s06 Subagent 的本质区别

s06 的 subagent 是一次性调用,队友则是持久执行单元:

s06 Subagent s13 队友
生命周期 一次调用后结束 WORK → IDLE → WORK,直到关机
上下文 只服务一个任务 跨任务保留
通信 返回一次结果 接收消息并发出事件
协作 单向委派 与 Lead 双向协作

TeammateRuntimecode.py)为每个队友保存独立的系统提示词、messages、工具表和当前任务,再在线程中运行 WORK / IDLE 循环。队友工作时,Lead 可以继续协调其他任务。leadagent 保留给运行时身份,MessageBus 仍允许把 lead 作为协调者收件箱。

从源码结构看,队友的 spawn 过程严格保证"先认领、后启动":

def spawn_teammate_thread(name, role, prompt, task_id=None,
                          require_plan=False):
    # 校验名字(1-64 位字母数字下划线短横线),拒绝保留名
    if name.lower() in RESERVED_TEAMMATE_NAMES:
        return f"Invalid teammate name: '{name}' is reserved by the runtime"
    ...
    if task_id:
        claimed = claim_task(task_id, owner=name)
        if not claimed.startswith("Claimed "):
            # 回滚 active_teammates / plan_gates / assignment_versions
            return f"Cannot spawn teammate '{name}': {claimed}"
    runtime = TeammateRuntime(name, role, prompt, task_id, require_plan)
    thread = threading.Thread(target=runtime.run, daemon=True)
    thread.start()

spawn_teammate 在线程启动前认领初始任务,认领失败时不会启动队友;队友没有任务时,文件和 Shell 工具会要求它先认领任务,而不是回退到仓库目录(current_cwd() 直接返回 Error: Claim a Task before using workspace tools.)。返回给 Lead 的提示也会明确写出:"End this turn; the runtime will deliver its events."

四、MessageBus:把通信放在模型上下文之外

Lead 和队友不能共享同一个 messages 数组,否则一个队友的工具结果会进入另一个队友的推理上下文。MessageBuscode.py)为每个 Agent 提供 .mailboxes/<name>.jsonl 收件箱:

class MessageBus:
    def send(self, from_agent, to_agent, content,
             msg_type="message", metadata=None):
        msg = {"from": from_agent, "to": to_agent,
               "content": content, "type": msg_type,
               "ts": time.time(), "metadata": metadata or {}}
        with self._changed:
            MAILBOX_DIR.mkdir(parents=True, exist_ok=True)
            with self._path(to_agent).open("a", encoding="utf-8") as handle:
                handle.write(json.dumps(msg, ensure_ascii=True) + "\n")
            self._changed.notify_all()

    def wait_for_messages(self, agent, timeout=None):
        deadline = None if timeout is None else time.monotonic() + timeout
        with self._changed:
            while not self.peek(agent):
                remaining = (None if deadline is None
                             else deadline - time.monotonic())
                if remaining is not None and remaining <= 0:
                    return []
                self._changed.wait(remaining)
            return self._read_unlocked(agent)

要点:

  • 收件箱是追加写的 JSONL 文件,锁保护并发读写,Condition 既能在消息到达时唤醒队友,也支持 IDLE 状态下的短时等待(timeout 到期返回空列表,供 IDLE 循环周期性扫描任务板);
  • read_inbox() 读取后立即删除收件箱文件(destructive read),保证消息只被消费一次;
  • 收件箱名通过 VALID_AGENT_NAME = re.compile(r"^[A-Za-z0-9_-]{1,64}$") 校验,且解析后路径必须仍在 .mailboxes/ 目录下,防止路径逃逸。测试 test_message_bus_rejects_unregistered_or_unsafe_recipients 验证了这一点。

五、收件箱事件由运行时投递,模型不轮询

因为 read_inbox() 是破坏性读取,Lead 只保留一个消费者 consume_lead_inbox()

def consume_lead_inbox() -> list[dict]:
    """Consume Lead events and update protocol state before model delivery."""
    msgs = BUS.read_inbox("lead")
    for msg in msgs:
        metadata = msg.get("metadata", {})
        request_id = metadata.get("request_id", "")
        if request_id and msg.get("type", "").endswith("_response"):
            match_response(msg["type"], request_id,
                           metadata.get("approve", False),
                           msg.get("from", ""), msg.get("to", ""))
    return msgs

CLI 主循环通过 select.select([sys.stdin], [], [], 0.25) 同时等待终端输入和 Lead 收件箱。新消息到达时,它会先消费收件箱、更新协议状态,再把 [Team events] 注入 history,启动新一轮 Lead 调用:

MessageBus → consume_lead_inbox
           → 更新协议状态
           → 把 [Team events] 注入 history
           → 启动新一轮 Lead 调用

终端会打印 [wake: N team event(s) -> new turn]。Lead 启动队友后会结束当前轮次,不用反复调用 list_teammatesget_task 等待结果。注意 check_inbox 不是模型工具——消息到达和消费属于运行时职责,模型只处理已经投递到上下文里的事件。

六、result 与 idle_notification 是两个事件

队友完成一项任务后,运行时按顺序发送两个事件(见 TeammateRuntime.work()code.py):

result:            "认证已重构,相关测试通过。"
idle_notification: "Waiting for more work."

result 回答"这项任务产出了什么",idle_notification 回答"这个队友能否继续接任务"。一个含糊的"完成了"无法同时表达这两种状态。源码中的判断细节:若队友处于 plan_gates == "pending"(计划等待审批),则不发 result、不发 idle_notification,状态置为 waiting_approval;只有 gate 非 pending 时才 release_completed_assignment()、置 idle 并发送两个事件。

空闲队友不会退出。直接消息或 ready task 会让它回到 WORK,shutdown_request 则会启动平滑关机握手。

七、IDLE 循环:先看收件箱,再找 ready task

队友进入 IDLE 后的等待逻辑是 wait_for_work()code.py):

def wait_for_work(self) -> bool:
    while True:
        inbox = BUS.wait_for_messages(self.name, IDLE_SCAN_INTERVAL)
        if inbox:
            if self.handle_inbox(inbox):
                return False        # 有效关机 → 退出循环
            if len(self.messages) > before:
                return True         # 有新工作消息 → 回到 WORK
            continue

        task = claim_next_task(self.name)
        if not task:
            continue
        self.messages.append({
            "role": "user",
            "content": (f"[Auto-claimed task {task.id}] {task.subject}\n"
                        f"{task.description}\nWork directory: {cwd}"),
        })
        return True

IDLE_SCAN_INTERVAL 为 2.0 秒。关机、计划审批和 Lead 的直接指令应该先于临时发现的工作:有消息时先处理消息;没有消息才扫描任务板。如果没有消息也没有 ready task,队友保持 IDLE。前置任务完成后,当前受阻的任务可能变为 ready(blockedBy 依赖全部 completed 才 can_start)。

八、发现与认领分离,认领必须原子执行

扫描只负责找候选任务(scan_unclaimed_tasks()):

def scan_unclaimed_tasks() -> list[Task]:
    with task_lock:
        ready = []
        for task in list_tasks():
            if (task.status != "pending" or task.owner is not None
                    or not can_start(task.id)):
                continue
            _, error = task_worktree_cwd(task)
            if not error:
                ready.append(task)
        return ready

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

def claim_task(task_id: str, owner: str = "agent") -> str:
    with task_store_lock():
        task = load_task(task_id)
        if task.status != "pending":
            return f"Task {task_id} is {task.status}, cannot claim"
        if task.owner:
            return f"Task {task_id} is already owned by {task.owner}"
        # 拒绝拥有未完成 assignment / in_progress 任务的 owner
        if not can_start(task_id):
            return f"Blocked by: {_incomplete_dependencies(task)}"
        cwd, error = task_worktree_cwd(task)
        if error:
            return f"Cannot claim {task_id}: {error}"
        task.owner = owner
        task.status = "in_progress"
        save_task(task)
        teammate_assignments[owner] = {"task_id": task.id, "cwd": cwd}
        advance_assignment_version(owner)
    return f"Claimed {task.id} ({task.subject})"

task_store_lock() 的实现值得注意:它先取线程级 threading.RLock,在"最外层"持锁时用 fcntl.flock 获取 .tasks/.lock 文件锁,并用线程局部深度计数支持重入(code.py)。这使原子性不仅覆盖同进程内的队友线程,也覆盖跨进程场景——测试 test_task_claim_is_atomic_across_processesmultiprocessing 起两个子进程同时认领同一任务,断言只有一个 Claimed 成功,且持久化状态为 in_progress

多个队友可以同时发现同一候选,但只有一个 claim 能把它推进到 in_progress。持有同一存储锁时,任务内容先写入临时文件(save_task.tmp + os.replace),再原子替换正式文件。队友完成当前任务后才能再认领下一项;worktree 绑定损坏时,认领会直接失败,不会回退到仓库目录。

九、认领后的工作复用同一个 WORK 循环

认领成功后,运行时把任务 ID、标题和描述放进队友的 messages:

任务板出现 ready task
  → IDLE 队友发现候选
  → claim_task 写入 owner 和 in_progress
  → 任务进入队友 messages
  → WORK
  → complete_task
  → result + idle_notification
  → IDLE

队友继续使用直接派发任务时的模型调用、文件工具、Shell、计划闸门、结果上报和关机协议。任务发现只是现有 WORK 循环的另一个入口。队友线程异常退出时,run()finally 块会 release_teammate_assignment():把未完成任务退回 pending、清空 owner 与 assignment,并向 Lead 发送 error 消息(测试 test_teammate_exception_releases_runtime_and_task_ownership 验证了这一回滚)。

十、由任务选择工具的工作目录:可选 worktree

Task.worktree 是可选字段:

@dataclass
class Task:
    id: str
    subject: str
    description: str
    status: str          # pending | in_progress | completed
    owner: str | None
    blockedBy: list[str]
    worktree: str | None = None

10.1 创建与绑定

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

create_worktree(name="auth-refactor", task_id="task_1a2b3c4d")

create_worktree 只提供给 Lead。它要求任务处于 pending、无人认领且尚未绑定,随后依次检查:

  1. 名称合法性(^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$,禁止 ..)与路径逃逸;
  2. 工作目录必须是 Git 仓库根(git rev-parse --show-toplevel);
  3. 分支 wt/<name>check-ref-format 合法性,且分支不存在、路径未被注册;
  4. 执行 git worktree add -b wt/<name> <path> HEAD 创建 checkout;
  5. 最后才把 task.worktree = name 写入任务(绑定)。

如果 Git 报告失败却已经留下分支或已注册的 checkout,运行时会返回 Partial operation,明确列出保留下来的工件(checkout 路径、已注册 worktree、分支),让任务保持未绑定,保留内容供人工恢复——对应测试 test_failed_git_add_reports_and_preserves_partial_artifactstest_binding_failure_retains_created_git_data_for_recovery。队友只有任务工具和文件工具,没有 worktree 创建权限。

10.2 assignment:cwd 的来源

认领任务时,运行时会把解析后的目录写入 teammate_assignments{owner: {"task_id", "cwd"}})。该队友的 bashread_filewrite_fileedit_fileglob 都通过 current_cwd()assignment_cwd(owner) 读取目录:

cwd, error = task_worktree_cwd(task)
if not error:
    teammate_assignments[owner] = {"task_id": task.id, "cwd": cwd}

语义要点:

  • 没有绑定 worktree 的任务解析到 WORKDIR(仓库目录);
  • 没有认领任务的队友不能使用这些工作区工具(返回认领错误);
  • complete_task(task_id, owner) 会检查调用者是否拥有这个进行中的任务;成功完成只记录结果,不会马上清除 assignment——直到当前模型轮次结束,后续工具调用仍使用这个任务目录(测试 test_completed_assignment_keeps_lead_in_worktree_until_turn_boundary 验证此行为)。队友回到 IDLE 时,运行时才在轮次边界释放 assignment;完成失败时也会保留目录,方便修正后重试;
  • 进程重启后,assignment_cwd() 可以根据持久化任务中的 owner 和 worktree 绑定恢复进行中的 assignment;同一 owner 已转到新任务时也会替换本地旧 lease;若绑定丢失或无效,它直接失败,不会把操作悄悄切回仓库目录(测试 test_in_progress_assignment_rehydrates_after_runtime_restarttest_stale_worktree_assignment 覆盖了这些路径)。

Worktree 只分开 Git 工作目录和分支,不是安全沙箱。Shell 命令仍能访问父进程有权访问的路径和资源。

10.3 Worktree 移除由宿主负责

模型可以创建任务绑定的 worktree,但不能移除它。remove_worktree(name, discard_changes=False) 是宿主函数(code.py),让用户或宿主先检查任务所有权、assignment lease 和 Git 状态。它会拒绝:

  • 绑定到 pending / in-progress 任务("complete it before removal");
  • 当前轮次仍在使用的 assignment lease("wait for the turn to end");
  • 未明确选择破坏性移除时,任何已跟踪、未跟踪或已忽略文件的改动(git status --porcelain --ignored 非空即拒绝)。

两种移除路径都保留仓库里的 wt/<name> 分支,包括没有 upstream 的干净本地提交(测试 test_clean_local_commit_survives_non_force_checkout_removal)。移除成功后,任务绑定被清空:

干净 worktree    → 宿主可移除目录,保留 wt/<name> 分支
有改动 worktree  → 由用户决定保留还是丢弃
待办/进行中任务  → 拒绝移除

任务完成与 worktree 清理互相独立:complete_task 记录任务结果;队友回到 IDLE 后,用户或宿主才检查、合并、保留或移除 worktree。测试 test_worktree_removal_is_host_only 确认了 Lead 的工具体系中不存在移除入口。

十一、类型化控制协议:request_id + 状态机

普通协作可以使用自由文本,关机和审批则不能依靠猜测消息意图,它们使用结构化消息与协议状态(code.py):

团队协议:shutdown 与 plan approval 两类请求/响应通过 request_id 关联

@dataclass
class ProtocolState:
    request_id: str
    type: str          # shutdown | plan_approval
    sender: str
    target: str
    status: str        # pending | approved | rejected
    payload: str
    work_version: int | None = None
    task_id: str | None = None
    created_at: float = field(default_factory=time.time)

pending_requests: dict[str, ProtocolState] = {}

11.1 关机协议

关机路径如下:

Lead 创建 pending 状态的关机请求(run_request_shutdown)
  → shutdown_request(request_id) 进入队友收件箱
  → 队友完成当前步骤
  → shutdown_response(request_id) 返回 Lead
  → request_id 找到原始请求
  → pending 变为 approved,队友循环退出

三道校验保证状态不会被乱改(apply_shutdown_request + match_response):request_id 把回复关联到请求;类型检查阻止 shutdown_responseplan_approval_response 互换;status 检查阻止同一回复重复生效("already approved/rejected")。方向校验也包含其中:关机请求必须 from == "lead"to == 该队友,响应必须来自被请求的队友本身(测试 test_shutdown_response_must_come_from_requested_teammatetest_shutdown_request_must_match_active_protocol 逐一验证)。队友收到有效关机请求后立即回发 shutdown_response(approve=True),handle_inbox 返回 True,run() 循环退出。

11.2 计划审批协议:闸门约束执行

计划协议方向相反:

Lead → plan_request
队友 → plan_approval_request(request_id, plan)
Lead → plan_approval_response(request_id, approve, feedback)

如果 Lead 在启动队友前就知道必须先看计划,可以调用 spawn_teammate(..., task_id=task.id, require_plan=True);运行时会先认领任务并打开闸门(plan_gates[name] = "required"),再启动线程(测试 test_required_plan_is_active_before_teammate_thread_starts 通过 patch 线程启动断言了"闸门先于线程"的顺序)。对已经运行的队友,Lead 可用 request_plan 要求其提交计划。

工具分发层负责执行闸门(_run_teammate_toolcode.py):

def _run_teammate_tool(name: str, block, handlers: dict) -> str:
    gate = plan_gates.get(name, "not_required")
    if block.name in {"bash", "write_file", "edit_file"}:
        if gate != "approved":
            if gate != "not_required":
                return (f"Blocked: plan status is {gate}. Submit or revise "
                        "the plan and wait for approval before changing the "
                        "workspace.")
        blocked = check_permission(block, prompt_user=False)
        if blocked:
            return blocked
    ...

状态是 requiredpendingrejected 时,队友可以读取文件、提交或修改计划,但不能运行 Shell 命令、写文件或编辑文件(测试 test_plan_gate_blocks_mutating_tools_until_approval)。

一个容易忽视的正确性细节是 work version 边界:提交计划时,_teammate_submit_plan 会记录队友当前的 task_idassignment_versions 版本号;Lead 侧 run_review_plan 与队友侧 apply_plan_response 在审批生效前都会核对两者仍一致。认领或释放任务会 advance_assignment_version(owner) 使旧审批失效(test_plan_approval_cannot_cross_assignment_boundary 验证:任务切换后审批返回"belongs to an earlier assignment",闸门保持 required);普通消息不会改变任务身份或审批状态。此外 request_id 还必须是该队友当前唯一的 pending 计划(plan_request_ids),防止旧响应误放行闸门(test_mismatched_plan_response_cannot_release_gate)。

队友不会直接从后台线程读取用户输入:遇到需要用户确认的危险命令或工作区外路径时,check_permission(block, prompt_user=False) 返回 permission 错误("Permission required: ask Lead to run this command."),由 Lead 与用户处理。

十二、一次完整运行

课程给出的端到端示例:

s13 >> 把后端重构拆到共享任务板,尽量并行完成配置、认证和测试。
       认证任务使用 worktree,保持现有接口,并确保测试通过。

Lead:我建议按 config、auth 和 tests 三个方向分工。
      是否启动团队?

s13 >> 开始吧

[task] config created
[task] auth created → worktree auth-refactor
[task] tests created
[claim] alice → config (cwd: repository)
[claim] bob → auth (cwd: .worktrees/auth-refactor)
[teammate] alice spawned
[teammate] bob spawned
[complete] auth
[bus] bob → lead (result) ...
[bus] bob → lead (idle_notification) ...
[wake: 2 team events → new turn]
Lead:我已收到认证任务的结果,接下来继续协调其余工作。

终端会显示用户请求、Lead 的团队方案、任务状态([create]/[claim]/[complete]/[unblocked])、认领结果、所选目录、结果、IDLE 切换和控制事件([bus][protocol])。用户不需要指定谁是 Lead,也不必提醒它检查收件箱。

十三、相对 s10 的变化

组件 s10 s13
Agent 单个 Agent 一个 Lead 加持久队友
用户流程 直接执行请求 先提团队方案,再确认启动
通信 文件收件箱加运行时投递
生命周期 一个循环 队友 WORK / IDLE / shutdown
共享工作 单 Agent 使用任务工具 IDLE 扫描加队友原子认领
工作目录 仓库 WORKDIR 必须认领任务;任务可选 worktree
结果上报 当前 Agent 输出 分开的 resultidle_notification
控制 类型化关机与计划审批协议
执行约束 无团队约束 必需计划会锁住修改型工具

十四、动手运行

运行前提(见 code.py 头部注释):pip install anthropic python-dotenv,并在 .env 中提供 ANTHROPIC_API_KEYMODEL_ID 环境变量。

cd learn-claude-code
python s13_agent_teams/code.py

输入一个自然需求:

把后端重构拆到共享任务板,在依赖允许时并行完成配置、认证和测试。
认证任务使用 worktree,保持现有接口,并在最后汇总结果。

Lead 提出团队方案后回复:

开始吧

建议观察的点:.tasks/ 如何从 pending 进入 in_progresscompleted.mailboxes/ 如何投递 resultidle_notification.worktrees/ 是否只为绑定的任务创建;直接消息是否先于任务板扫描;complete_task 失败后队友的工作目录是否保持不变。

不消耗 API 额度时,也可以直接阅读回归测试 tests/test_agent_teams_runtime.py——它把 code.py 加载进临时目录(mock 掉 anthropic/dotenv),覆盖了本节全部关键不变量,例如 test_idle_claim_is_atomic_across_teammates(同进程多队友原子认领)、test_plan_gate_blocks_mutating_tools_until_approvaltest_remove_worktree_refuses_dirty_checkout_by_defaulttest_remove_worktree_treats_ignored_files_as_uncommitted_data 等。

十五、小结与延伸

s13 给出的团队运行时可以概括为四条设计原则:

  1. 确认前置:新增 Agent 是权限边界变更,Lead 只能提案,用户确认后才 spawn_teammate
  2. 通信外置:消息、结果、控制事件都走文件收件箱,模型既不轮询、也不共享 messages 数组,运行时负责投递与唤醒;
  3. 状态显式化:任务归属、cwd 租约(assignment)、协议状态(request_id + status + work version)全部持久化或集中管理,认领原子执行,失败路径一律 fail-closed 而非静默回退;
  4. 能力分层:创建 worktree 只对 Lead 开放,移除 worktree 只对宿主开放,队友只拿到完成任务所需的最小工具集,且工作目录永远由任务决定。

在课程脉络中,Lead 和队友目前只能调用直接写在 code.py 里的工具;接入 Jira、部署平台或知识库时,Harness 还要为每个外部系统分别编写工具定义和调用逻辑。下一步 s14 MCP Tools 通过统一的发现与调用协议,在运行时连接外部服务并把它们的工具加入工具池。

登录后查看全文
热门项目推荐
相关项目推荐