Harness Engineering 再进阶:多 Agent 编排、上下文隔离与长任务恢复
单个 Coding Agent 能完成一次修改,不代表它能稳定完成跨模块、跨小时甚至跨会话的工程任务。真正困难的不是“同时启动更多 Agent”,而是如何划分责任、隔离状态、表达依赖、验证交付物、处理冲突,并在任意一步失败后从正确位置恢复。
系列文章:
- Harness Engineering 项目实战:把 AI Coding Agent 从“会写代码”变成“可靠交付”
- Harness Engineering 延续篇:用 Evals、Trace 与 Failure Mining 构建自进化质量闭环
本文继续以 Spring Boot 多模块能源平台为背景,构建一个生产级多 Agent Orchestrator。
一、为什么单 Agent 会在长任务中失速
假设需求是:
为设备离线事件增加原因分类,管理端提供查询接口,任务模块每天生成统计结果,开放接口保持向后兼容,并补齐数据库脚本和测试。
这个任务横跨领域模型、管理服务、调度服务、开放接口和数据库。把它一次性交给单个 Agent,常见结果有四类。
1.1 上下文腐化
Agent 读取的代码、日志和测试输出越来越多,早期约束被挤到上下文深处。长上下文增加的是容量,不一定增加注意力质量。
1.2 计划漂移
开始时计划修改五个模块,执行到中途只记得眼前的编译错误,最后把局部绿灯误认为任务完成。
1.3 状态不可恢复
会话中断后,新的 Agent 不知道哪些决策已经确定、哪些验证已完成,只能重新考古或冒险接着改。
1.4 并行写冲突
多个 Agent 在同一工作区编辑同一文件,最终结果取决于写入顺序。并行带来的不是吞吐,而是竞态条件。
多 Agent Harness 的核心不是角色扮演,而是分布式系统设计:
任务拆分 → DAG
责任边界 → Contract
并发安全 → Workspace Isolation + Ownership
通信 → Artifact,而不是完整聊天记录
一致性 → 独立验证 + 串行集成
故障恢复 → Checkpoint + Idempotency
资源治理 → Budget + Lease + Backpressure
二、总体架构:控制面与执行面分离
┌─────────────────────┐
需求 / 验收标准 ──────→│ Planner │
└──────────┬──────────┘
│ Task DAG
▼
┌─────────────────────────────────────────────────────────┐
│ Orchestrator 控制面 │
│ Scheduler │ Lease │ Budget │ Checkpoint │ Policy │ Trace │
└──────┬──────────────┬──────────────┬───────────────┬─────┘
│ │ │ │
▼ ▼ ▼ ▼
Search Agent Domain Agent API Agent Test Agent
只读上下文 worktree-A worktree-B worktree-C
│ │ │ │
└──────────────┴───────┬──────┴───────────────┘
│ Artifact Manifests
▼
Integrator + Evaluator
│
▼
Maven / ArchUnit / Contract Tests
│
通过 ─────┴───── 失败回灌
这里有三个硬边界:
- Planner 不直接改代码,只生成可验证任务图;
- Worker 只写自己拥有的路径,不能直接合并结果;
- Integrator 不重新发明实现,只按依赖顺序集成已验证 Artifact。
三、任务图:把计划从自然语言变成可执行 DAG
3.1 Task Contract
# harness/orchestration/contracts.py
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Literal
class TaskStatus(str, Enum):
PENDING = "pending"
READY = "ready"
RUNNING = "running"
VERIFYING = "verifying"
SUCCEEDED = "succeeded"
FAILED = "failed"
BLOCKED = "blocked"
@dataclass(frozen=True)
class AcceptanceCheck:
kind: Literal["command", "file", "semantic"]
value: str
@dataclass
class TaskNode:
id: str
goal: str
dependencies: set[str]
read_paths: tuple[str, ...]
write_paths: tuple[str, ...]
checks: tuple[AcceptanceCheck, ...]
risk: Literal["low", "medium", "high"] = "medium"
max_attempts: int = 2
status: TaskStatus = TaskStatus.PENDING
attempts: int = 0
@dataclass
class TaskGraph:
goal: str
nodes: dict[str, TaskNode] = field(default_factory=dict)
一个任务节点必须回答:
- 产出什么,而不是“研究一下”;
- 依赖谁的产物;
- 可以读取和修改哪些路径;
- 怎样独立判断完成;
- 失败多少次后升级给人。
3.2 校验 DAG 与写路径冲突
# harness/orchestration/graph.py
from __future__ import annotations
from collections import deque
from harness.orchestration.contracts import TaskGraph
class InvalidTaskGraph(ValueError):
pass
def topological_order(graph: TaskGraph) -> list[str]:
indegree = {node_id: 0 for node_id in graph.nodes}
outgoing = {node_id: [] for node_id in graph.nodes}
for node in graph.nodes.values():
unknown = node.dependencies - graph.nodes.keys()
if unknown:
raise InvalidTaskGraph(f"{node.id} 依赖未知节点:{sorted(unknown)}")
indegree[node.id] = len(node.dependencies)
for dependency in node.dependencies:
outgoing[dependency].append(node.id)
queue = deque(sorted(key for key, value in indegree.items() if value == 0))
result: list[str] = []
while queue:
current = queue.popleft()
result.append(current)
for child in outgoing[current]:
indegree[child] -= 1
if indegree[child] == 0:
queue.append(child)
if len(result) != len(graph.nodes):
raise InvalidTaskGraph("任务图存在循环依赖")
return result
def patterns_overlap(left: str, right: str) -> bool:
"""保守判定:路径前缀相交即认为可能冲突。"""
left_prefix = left.removesuffix("/**").rstrip("/")
right_prefix = right.removesuffix("/**").rstrip("/")
return left_prefix.startswith(right_prefix) or right_prefix.startswith(left_prefix)
def assert_parallel_safe(graph: TaskGraph) -> None:
nodes = list(graph.nodes.values())
for index, left in enumerate(nodes):
for right in nodes[index + 1:]:
ordered = left.id in right.dependencies or right.id in left.dependencies
overlap = any(
patterns_overlap(a, b)
for a in left.write_paths
for b in right.write_paths
)
if overlap and not ordered:
raise InvalidTaskGraph(
f"{left.id} 与 {right.id} 写路径重叠,但没有依赖顺序"
)
这是保守策略。它宁可少并行,也不允许两个 Worker 靠运气覆盖同一文件。更复杂的系统可以分析 Java Symbol 或 Git diff,但路径所有权仍是最容易审计的第一道边界。
3.3 业务任务 DAG
T1 调查现有设备离线链路(只读)
├── T2 修改 energy-domain 模型与枚举
│ ├── T3 修改 manage 查询与 DTO
│ └── T4 修改 task 日统计逻辑
├── T5 编写 API 黑盒验收测试
└── T6 校验 open 模块向后兼容
T2 + T3 + T4 + T5 + T6
└── T7 串行集成、全量验证与独立 Review
调查任务可以先行,T3~T6 在路径不冲突时并行,最终集成必须串行。DAG 表达的是依赖关系,不是让所有节点同时启动。
四、调度器:Ready Queue、Lease 与背压
4.1 只有依赖完成的任务才能运行
# harness/orchestration/scheduler.py
from __future__ import annotations
from harness.orchestration.contracts import TaskGraph, TaskNode, TaskStatus
class Scheduler:
def __init__(self, graph: TaskGraph, max_parallelism: int = 3) -> None:
self.graph = graph
self.max_parallelism = max_parallelism
def refresh(self) -> None:
for node in self.graph.nodes.values():
if node.status is not TaskStatus.PENDING:
continue
dependency_states = [
self.graph.nodes[item].status for item in node.dependencies
]
if any(state in {TaskStatus.FAILED, TaskStatus.BLOCKED}
for state in dependency_states):
node.status = TaskStatus.BLOCKED
elif all(state is TaskStatus.SUCCEEDED for state in dependency_states):
node.status = TaskStatus.READY
def claimable(self) -> list[TaskNode]:
self.refresh()
running = sum(
node.status in {TaskStatus.RUNNING, TaskStatus.VERIFYING}
for node in self.graph.nodes.values()
)
capacity = max(0, self.max_parallelism - running)
ready = [
node for node in self.graph.nodes.values()
if node.status is TaskStatus.READY
]
# 高风险任务优先,减少尾部关键路径等待。
priority = {"high": 0, "medium": 1, "low": 2}
return sorted(ready, key=lambda item: (priority[item.risk], item.id))[:capacity]
仅设置 max_parallelism 还不够。外部模型 API、构建 CPU、数据库 Fixture 都可能成为瓶颈:
P a r a l l e l i s m ≤ min ( M o d e l Q u o t a , S a n d b o x C a p a c i t y , T e s t C a p a c i t y ) Parallelism \leq \min(ModelQuota, SandboxCapacity, TestCapacity) Parallelism≤min(ModelQuota,SandboxCapacity,TestCapacity)
当 Maven 测试队列积压时,调度器应暂停新 Worker,而不是继续生成更多等待验证的代码。这就是 Backpressure。
4.2 Lease 防止任务被重复领取
分布式 Worker 可能崩溃,不能只用内存中的 RUNNING 标志。任务领取需要带租约:
UPDATE agent_task
SET status = 'RUNNING',
owner = :worker_id,
lease_until = :now + INTERVAL '5 minutes',
version = version + 1
WHERE id = :task_id
AND status = 'READY'
AND version = :expected_version;
受影响行数为 1 才算领取成功。Worker 定期续租;租约过期后,Orchestrator 可以重新派发。version 提供乐观锁,避免两个 Worker 同时拿到任务。
但租约不能保证“恰好执行一次”。Worker 可能完成写入后、上报成功前崩溃,因此所有副作用都要尽量幂等。
五、Workspace 隔离:共享仓库,独立写入
5.1 为什么使用 Git Worktree
每个 Worker 共用 .git 对象库,但拥有独立工作目录和分支,创建速度远快于完整 Clone:
repo-main/ # Integrator,只负责集成
worktrees/run-42-T2/ # Domain Worker
worktrees/run-42-T3/ # Manage Worker
worktrees/run-42-T5/ # Test Worker
# harness/orchestration/workspace.py
from __future__ import annotations
import subprocess
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class Workspace:
task_id: str
branch: str
path: Path
base_commit: str
class WorktreeManager:
def __init__(self, repository: Path, root: Path) -> None:
self.repository = repository.resolve()
self.root = root.resolve()
self.root.mkdir(parents=True, exist_ok=True)
def create(self, run_id: str, task_id: str, base_commit: str) -> Workspace:
branch = f"agent/{run_id}/{task_id}"
path = self.root / f"{run_id}-{task_id}"
subprocess.run(
["git", "worktree", "add", "-b", branch, str(path), base_commit],
cwd=self.repository, check=True, capture_output=True, text=True,
)
return Workspace(task_id, branch, path, base_commit)
def diff(self, workspace: Workspace) -> str:
completed = subprocess.run(
["git", "diff", "--binary", workspace.base_commit],
cwd=workspace.path, check=True, capture_output=True, text=True,
)
return completed.stdout
删除 Worktree、分支和临时资源属于生命周期清理,应由控制面在确认 Artifact 已归档后执行,不授权 Worker 自行清理。
5.2 路径权限必须在写入时检查
只在任务 Prompt 中说“只能修改 manage 模块”不够。每次写入前都要检查:
import fnmatch
from pathlib import Path
def assert_write_allowed(relative_path: str, patterns: tuple[str, ...]) -> None:
normalized = relative_path.replace("\\", "/")
if Path(normalized).is_absolute() or ".." in Path(normalized).parts:
raise PermissionError(f"路径越界:{relative_path}")
if not any(fnmatch.fnmatch(normalized, pattern) for pattern in patterns):
raise PermissionError(f"任务无权写入:{relative_path}")
工作区隔离解决并发污染,路径策略解决任务越权,两者缺一不可。
六、Artifact 协议:Agent 之间传递证据,不传递噪声
把完整聊天历史传给下游 Agent,会把搜索日志、失败尝试和过期假设一并传播。更稳定的方式是输出结构化 Artifact Manifest:
# harness/orchestration/artifacts.py
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import json
from pathlib import Path
@dataclass(frozen=True)
class ArtifactManifest:
task_id: str
base_commit: str
patch_file: str
patch_sha256: str
changed_files: tuple[str, ...]
decisions: tuple[str, ...]
verification: tuple[str, ...]
residual_risks: tuple[str, ...]
def create_manifest(
task_id: str,
base_commit: str,
patch: bytes,
output_dir: Path,
changed_files: list[str],
decisions: list[str],
verification: list[str],
residual_risks: list[str],
) -> ArtifactManifest:
output_dir.mkdir(parents=True, exist_ok=True)
patch_path = output_dir / f"{task_id}.patch"
patch_path.write_bytes(patch)
manifest = ArtifactManifest(
task_id=task_id,
base_commit=base_commit,
patch_file=str(patch_path),
patch_sha256=hashlib.sha256(patch).hexdigest(),
changed_files=tuple(changed_files),
decisions=tuple(decisions),
verification=tuple(verification),
residual_risks=tuple(residual_risks),
)
(output_dir / f"{task_id}.json").write_text(
json.dumps(asdict(manifest), ensure_ascii=False, indent=2),
encoding="utf-8",
)
return manifest
Manifest 是 Agent 间的稳定接口:
patch是可重放交付物;sha256防止交付后被静默替换;decisions保存影响后续任务的结论;verification是已执行证据,不是“应该能通过”;residual_risks明确不知道什么。
下游 Agent 默认只读取 Manifest,需要证据时再按路径读取 Patch,实现渐进式披露。
七、上下文路由:给每个任务最小充分信息
7.1 Context Pack
# harness/orchestration/context_router.py
from dataclasses import dataclass
@dataclass(frozen=True)
class ContextPack:
global_rules: str
task_contract: str
architecture_slice: str
dependency_manifests: tuple[str, ...]
relevant_files: tuple[str, ...]
token_budget: int
ROUTES = {
"domain-model": {
"docs": ("docs/domain-glossary.md",),
"skills": ("skills/domain-model-change/SKILL.md",),
"max_tokens": 18_000,
},
"api-change": {
"docs": ("docs/open-api-contract.md",),
"skills": ("skills/api-change/SKILL.md",),
"max_tokens": 22_000,
},
"scheduled-job": {
"docs": ("docs/task-module.md",),
"skills": ("skills/xxl-job/SKILL.md",),
"max_tokens": 20_000,
},
}
Context Router 根据任务类型组装上下文,而不是把全部 Skill、MCP Tool 和文档注入每个 Worker。
7.2 信息优先级
上下文超预算时,按以下顺序保留:
1. 原始任务与完成契约
2. 安全和写路径边界
3. 直接依赖 Artifact 的决策
4. 当前修改相关代码
5. 架构切片与领域词汇
6. 历史示例
7. 搜索过程和成功日志
压缩不能只按时间删除旧消息。最早出现的业务约束,往往比最近一次成功的 Maven 日志更重要。
八、Orchestrator:把各组件连接成可恢复状态机
# harness/orchestration/orchestrator.py
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
from harness.orchestration.contracts import TaskGraph, TaskNode, TaskStatus
from harness.orchestration.scheduler import Scheduler
class WorkerPool(Protocol):
def submit(self, node: TaskNode) -> str:
...
def poll(self) -> list[dict]:
...
class CheckpointStore(Protocol):
def save(self, graph: TaskGraph) -> None:
...
@dataclass
class Orchestrator:
graph: TaskGraph
scheduler: Scheduler
workers: WorkerPool
checkpoints: CheckpointStore
def tick(self) -> None:
# 先消费完成事件,再派发新任务,避免突破并发上限。
for event in self.workers.poll():
node = self.graph.nodes[event["taskId"]]
if event["type"] == "verified":
node.status = TaskStatus.SUCCEEDED
elif event["type"] == "retryable_failure":
node.attempts += 1
node.status = (
TaskStatus.READY
if node.attempts < node.max_attempts
else TaskStatus.BLOCKED
)
elif event["type"] in {"policy_denied", "ambiguous_requirement"}:
node.status = TaskStatus.BLOCKED
else:
node.status = TaskStatus.FAILED
self.checkpoints.save(self.graph)
for node in self.scheduler.claimable():
node.status = TaskStatus.RUNNING
self.workers.submit(node)
self.checkpoints.save(self.graph)
def finished(self) -> bool:
terminal = {TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.BLOCKED}
return all(node.status in terminal for node in self.graph.nodes.values())
这段代码刻意没有写成一个巨大的 while True。tick() 是幂等状态推进单元,进程重启后可以从 Checkpoint 重建 Graph,再继续调度。
九、失败恢复:重试不是默认答案
9.1 错误分类决定恢复动作
| 错误 | 是否重试 | 恢复策略 |
|---|---|---|
| API 429 / 临时网络失败 | 是 | 指数退避,保持原预算 |
| Sandbox 崩溃 | 是 | 从 Base Commit 重建,重放已验 Artifact |
| 编译失败 | 有条件 | 将精简错误回灌同一 Worker |
| 写路径越权 | 否 | 立即阻断并审计 |
| 需求歧义 | 否 | 请求人工澄清,冻结下游节点 |
| 架构测试失败 | 有条件 | 回到责任 Worker,不由 Integrator 临时修补 |
| Patch 冲突 | 否 | 重新基于最新集成点执行受影响节点 |
9.2 幂等键
为防止 Worker 超时后重复提交,每个副作用使用幂等键:
idempotency_key = SHA256(run_id + task_id + attempt + operation)
Artifact Store 对同一键只接受一次写入;重复结果若哈希一致则返回已有记录,不一致则标记为冲突。外部评论、工单更新等操作也必须使用幂等键。
9.3 Checkpoint 保存什么
{
"runId": "run-42",
"goalHash": "sha256:...",
"baseCommit": "a71c...",
"harnessVersion": "0.5.0",
"nodes": {
"T2": {
"status": "succeeded",
"attempts": 1,
"artifact": "sha256:9b2f..."
},
"T3": {
"status": "running",
"attempts": 1,
"leaseUntil": "2026-09-02T11:20:00Z"
}
},
"budgets": {
"tokensUsed": 128430,
"wallClockSeconds": 1840,
"costUsd": 6.72
}
}
Checkpoint 记录可验证状态,不保存冗长思维过程。恢复时重新加载任务契约、Artifact 和必要代码,比恢复完整聊天更稳定。
十、集成策略:Patch、依赖顺序与冲突重算
10.1 为什么 Integrator 不应该“顺手修一下”
如果 Integrator 在合并 T3 时发现错误并直接改代码,责任边界和验证证据都会失真。正确流程是:
Artifact 哈希校验
↓
路径所有权校验
↓
按 DAG 顺序应用 Patch
↓
局部冲突?──→ 原 Worker 基于新 Base 重新执行
↓
集成态全量 Sensor
↓
独立 Evaluator
自动文本冲突解决只适合低风险机械变更。两个 Agent 对同一方法做语义修改时,应重新规划或指定唯一 Owner。
10.2 乐观并发与重算
设 Worker 开始时基线为 B 0 B_0 B0,集成时主线已变为 B 1 B_1 B1。若 Patch P P P 能干净应用,也不能直接认为语义正确:
V a l i d ( P , B 0 ) ⇏ V a l i d ( P , B 1 ) Valid(P, B_0) \nRightarrow Valid(P, B_1) Valid(P,B0)⇏Valid(P,B1)
因此必须在 B 1 + P B_1 + P B1+P 上重新运行该节点的验收检查。若依赖契约发生变化,则废弃旧 Artifact,重新执行节点,而不是机械解决冲突。
十一、人类闸门:把人放在信息增益最高的位置
不是每个节点都需要人工确认。可以根据风险动态插入 Gate:
from dataclasses import dataclass
@dataclass(frozen=True)
class RiskSignal:
database_migration: bool = False
public_api_breaking: bool = False
production_config: bool = False
permission_change: bool = False
evaluator_disagreement: bool = False
def requires_human_approval(signal: RiskSignal) -> bool:
return any((
signal.database_migration,
signal.public_api_breaking,
signal.production_config,
signal.permission_change,
signal.evaluator_disagreement,
))
向人请求批准时,不应倾倒全部 Trace,而应给决策包:
决策:offline_reason 是否增加 NOT NULL 约束?
背景:历史设备记录没有该值。
方案 A:字段可空,兼容旧数据;查询端映射 UNKNOWN。
方案 B:非空并执行回填;需要数据库变更窗口。
建议:A。原因是本任务要求向后兼容且未授权迁移。
影响:A 保留数据语义不完整风险;B 增加发布风险。
人类最有价值的工作是做语义和风险取舍,不是替 Agent 阅读第 3000 行编译日志。
十二、生产实战:跨模块离线原因统计
把前述需求拆成以下 Contract:
| 节点 | Owner 路径 | 输入 Artifact | 验收 |
|---|---|---|---|
| T1 调查 | 全仓只读 | 原始需求 | 输出调用链与风险 |
| T2 领域模型 | core/energy-domain/** | T1 | domain tests |
| T3 管理接口 | platform-energy-manage/** | T1、T2 | MockMvc contract |
| T4 日统计任务 | platform-energy-task/** | T1、T2 | 日期边界测试 |
| T5 开放接口 | platform-energy-open/** | T1、T2 | OpenAPI diff |
| T6 集成 | 无业务写权限 | T2~T5 | 全量 Maven + Review |
几个关键设计:
- T2 先确定枚举值和空值语义,下游只消费其 Manifest;
- T3、T4、T5 分属不同模块,可在独立 Worktree 并行;
- T4 必须包含月末、闰日和补跑日期 Fixture;
- T5 的目标可能只是证明“不需要修改”,无变更也是合法 Artifact;
- T6 发现冲突时退回责任节点,不获得任意业务写权限。
端到端状态可能是:
T1 succeeded
T2 succeeded
T3 succeeded
T4 retryable_failure → succeeded
T5 succeeded(no-change)
T6 semantic_review → human_gate(database nullable decision) → succeeded
这比“启动五个 Agent,一会儿再看结果”多了很多结构,但结构正是可靠性的来源。
十三、衡量多 Agent 系统,而不是只看总耗时
13.1 关键指标
| 指标 | 说明 |
|---|---|
| Critical Path Duration | DAG 最长依赖路径耗时 |
| Parallel Efficiency | 串行预计耗时 / 实际并行耗时 |
| Rework Rate | 因上游变化而重做的节点比例 |
| Conflict Rate | Artifact 集成冲突比例 |
| Context Efficiency | 有效上下文 Token / 总输入 Token |
| Recovery Success Rate | 中断后无需人工即可恢复的比例 |
| Human Gate Precision | 人工介入中真正需要决策的比例 |
| Cost per Reliable Change | 每个通过独立验收的变更成本 |
13.2 并行效率的陷阱
若各节点耗时为 t i t_i ti,理想串行耗时是:
T s e r i a l = ∑ i t i T_{serial}=\sum_i t_i Tserial=i∑ti
并行耗时至少受关键路径约束:
T p a r a l l e l ≥ max p a t h p ∑ i ∈ p t i T_{parallel}\geq \max_{path\ p}\sum_{i\in p}t_i Tparallel≥path pmaxi∈p∑ti
实际还要加创建 Sandbox、加载上下文、验证和集成成本。任务过小时,多 Agent 反而更慢。经验上,只有当子任务满足“边界清晰、可独立验证、产物接口稳定”时,才值得并行。
十四、常见反模式
14.1 用角色名称代替任务边界
“你是资深后端专家”不能替代 write_paths、验收条件和返回 Schema。
14.2 所有 Agent 共享一段长对话
这不是协作,而是共享污染。通过 Manifest 交换结果,通过 Trace 保留证据。
14.3 Planner 一次性决定全部细节
早期信息不足。允许在节点完成后细化下游 Contract,但任何 DAG 变更都必须版本化。
14.4 失败后无限重试
重试只适合瞬态错误。确定性失败应修正输入或实现,歧义应升级给人。
14.5 并行 Agent 直接合并主分支
Worker 只能产出 Patch/Artifact;Integrate、发布和外部副作用属于独立权限域。
14.6 用 Agent 数量衡量成熟度
十个互相干扰的 Agent 不如两个边界清晰的 Worker。吞吐来自可并行的任务结构,不来自界面上更多头像。
十五、落地路线
阶段一:可恢复的单 Agent
- 状态落盘;
- 明确迭代、时间和成本预算;
- 会话中断后从 Checkpoint 恢复;
- 将聊天历史替换为结构化 Hand-off。
阶段二:只读子 Agent
- 把代码搜索、调用链分析和日志归纳隔离出去;
- 主线程只接收结论、证据路径和风险;
- 测量 Context Token 是否下降。
阶段三:隔离写 Worker
- 引入 Worktree/Sandbox;
- 定义写路径所有权;
- 用 Artifact Manifest 交付 Patch;
- Integrator 串行应用并重新验证。
阶段四:DAG 调度与治理
- 增加 Lease、Backpressure 和幂等键;
- 按风险插入 Human Gate;
- Eval 覆盖并发、恢复和冲突场景;
- 依据关键路径与返工率优化拆分粒度。
十六、结语
多 Agent 系统的难点从来不是“如何调用多个模型”,而是如何管理多个不完全可靠的执行者。它本质上综合了工作流引擎、分布式调度、权限系统、测试平台和可观测性工程。
一套可靠的编排 Harness 应满足:
任务有契约,而不是只有角色
并行有隔离,而不是共享目录
交付有 Artifact,而不是口头总结
完成有 Judge,而不是自我声明
失败有分类,而不是盲目重试
中断有 Checkpoint,而不是重头开始
高风险有人类闸门,而不是无限自治
单 Agent 的上限由上下文窗口约束,多 Agent 的上限则由协议质量约束。模型可以不断更换,但清晰的任务图、最小权限、可验证产物和故障恢复机制,会成为长期有效的工程资产。
541



被折叠的 条评论
为什么被折叠?



