Skip to content

Repository files navigation

Agent Evolve:从 Naive Agent 演进到 Harness Agent

agent-evolve 是一个面向智能体工程化实践的教学与实验项目。 项目通过对比一个故意保留八大故障点的 Naive Agent,逐步构建具备循环控制、工具治理、上下文管理、外部验证、权限门禁、预算控制、进度审计和断点恢复能力的 Harness Agent。

当前工程不是简单复制示例代码,而是把各项机制真正接入同一条 Agent 主循环,并通过自动化测试验证关键闭环。


1. 项目目标

Naive Agent 能够调用大模型和工具,但缺少稳定运行所需的工程约束。本项目重点治理以下八类故障:

故障编号 故障模式 Naive Agent 中的表现 Harness 治理方向
循环失控 while True 无最大轮次,可能无限调用模型和工具 最大轮次、明确终止状态
Context 溢出 消息持续追加,没有压缩、摘要和窗口控制 Token 估算、历史摘要、完整消息块保留
上下文基线漂移 每轮修改 System Prompt,破坏提示词一致性 固定 System Prompt、恢复一致性校验
Tool 错误吞没 工具异常被转换为空字符串,模型无法判断失败原因 结构化错误、异常类型和原因回写
状态丢失 进程中断后无法恢复,已完成步骤可能重放 原子 Checkpoint、resume=True
权限缺口 工具参数、文件路径和危险命令没有独立检查 Permission Gate、参数校验、Workspace 沙箱
缺少自动化评审 模型自行声称完成,没有外部验证 pytest Verifier、Generator-Evaluator
成本失控 没有 Token、费用、子 Agent 深度和轮次限制 Token Budget、费用上限、子 Agent 硬限制

2. 整体架构

flowchart LR
    User["用户任务"] --> Demo["demo.py"]
    Demo --> Agent["HarnessAgent"]

    Agent --> Context["Context Management"]
    Agent --> Budget["Token Budget"]
    Agent --> Hooks["Hooks"]
    Agent --> Tools["Tool Registry"]
    Agent --> Verifier["Verification Loop"]
    Agent --> Checkpoint["Checkpoint Recovery"]

    Hooks --> Permission["Permission Gate"]
    Hooks --> Progress["Progress Tracking"]

    Tools --> Planner["Feature List"]
    Tools --> Workspace["Workspace Tools"]

    Agent -. 可选扩展 .-> Subagent["Subagents"]
    Agent -. 可选扩展 .-> Evaluator["Generator-Evaluator"]
Loading

设计原则

  1. 单一主循环:真正的 Agent Loop 只存在于 HarnessAgent.run()
  2. 机制必须接入闭环:上下文压缩、预算、权限和验证不是孤立工具函数。
  3. 安全判断外置:模型不能自行决定是否有权限执行危险操作。
  4. 完成必须可验证:模型声称完成不等于任务完成。
  5. 进度与恢复分离:Progress 用于审计,Checkpoint 用于精确恢复。
  6. 扩展状态实例化:Planner 使用会话实例,不使用模块全局变量。
  7. 依赖可注入:模型客户端、Verifier 和 Hooks 均可替换,便于测试。

3. 目录结构

agent-evolve/
├── .env                              # DeepSeek/OpenAI-compatible 配置
├── demo.py                           # 主工程运行入口
├── pyproject.toml                    # Python 项目与依赖配置
├── uv.lock                           # uv 依赖锁文件
├── README.md                         # 项目总说明
├── HARNESS_MECHANISM_MATRIX.md       # 机制—故障—文件详细矩阵
├── HARNESS_REVIEW.md                 # 示例代码审查记录
│
├── naive_agent/
│   ├── __init__.py
│   ├── naive_agent_demo.py           # 故意保留八大故障点的对照实现
│   └── test_calculator.py            # 教学用计算器与测试
│
├── harness_agent/
│   ├── __init__.py                   # 公共 API
│   ├── agent.py                      # 唯一 Agent 主循环
│   ├── budget.py                     # Token 与费用硬预算
│   ├── checkpoint.py                 # 原子快照与断点恢复
│   ├── config.py                     # 集中配置与环境变量加载
│   ├── context.py                    # 上下文估算、分组和压缩
│   ├── core.py                       # 函数式兼容入口
│   ├── evaluator.py                  # Generator-Evaluator
│   ├── hooks.py                      # 生命周期事件与安全 Gate
│   ├── permission.py                 # 危险工具和命令权限策略
│   ├── planner.py                    # 会话级 Feature List
│   ├── progress.py                   # JSONL 进度与审计日志
│   ├── subagent.py                   # 子 Agent 委托
│   ├── tools.py                      # 工具注册、Schema、参数校验和沙箱
│   └── verifier.py                   # 独立 pytest 验证器
│
└── tests/
    ├── __init__.py
    ├── test_harness_agent.py         # 八大故障治理测试
    ├── test_harness_extensions.py    # 扩展机制及主循环集成测试
    └── test_verifier_real.py         # 真实 pytest 子进程测试

4. 十二个 Harness 机制

当前工程在参考表的 11 个机制基础上,增加了独立的 Checkpoint Recovery,共计 12 个机制。

编号 机制名称 主要治理故障 实现文件 实现状态
Agent Loop ① 循环失控、③ Prompt 漂移 agent.pycore.py 已接入主循环
Tool Use ④ Tool 错误吞没、⑥ 参数缺口 tools.pyagent.py 已接入主循环
Progress Tracking ⑤ 执行过程不可见、缺少审计 progress.pyhooks.py 已通过 Hook 接入
Context Management ② Context 溢出、③ 基线漂移 context.pyagent.py 已接入每轮模型调用前
Feature List ② 长任务膨胀、⑤ 任务进度丢失 planner.pydemo.py 已注册为 todo 工具
Verification Loop ⑦ 模型自判完成、④ 下游错误 verifier.pyagent.py 已作为完成门禁
Subagents ② 父上下文膨胀、⑧ 子任务失控 subagent.pytools.py 可选扩展
Generator-Evaluator ⑦ 缺少自动化候选评审 evaluator.py 可选扩展
Permission Gate ⑥ 权限缺口 permission.pytools.pyagent.py 已通过 Gate 接入
Hooks 贯穿全部机制 hooks.pyagent.py 已接入生命周期
Token Budget ⑧ 成本失控 budget.pyagent.py 已接入调用前后
Checkpoint Recovery ⑤ 状态丢失 checkpoint.pyagent.py 已支持恢复

更详细的机制矩阵见 HARNESS_MECHANISM_MATRIX.md


4.1 八大故障点治理示例代码

下面的代码分别展示 Naive Agent 中八类故障的典型表现,以及当前 Harness 的解决方式。 示例默认已经准备好以下基础对象:

from harness_agent import (
    HarnessAgent,
    HarnessConfig,
    HookManager,
    PermissionGate,
    create_workspace_tools,
    pytest_verifier,
)

config = HarnessConfig.from_env()
tools = create_workspace_tools(
    config.workspace,
    timeout=config.tool_timeout_seconds,
)
verifier = pytest_verifier(
    config.workspace,
    "tests",
    timeout=config.tool_timeout_seconds,
)

故障点①:循环失控

故障代码

# 没有最大轮次;如果模型一直返回 tool_calls,循环不会结束。
while True:
    response = client.chat.completions.create(...)
    execute_tool_calls(response)

Harness 解决方式

config.max_steps = 10

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
)
result = agent.run("检查并修复测试")

if result["status"] == "max_steps":
    print("达到最大轮次,已安全停止")
    print("已执行轮次:", result["steps"])

HarnessAgent.run() 的主循环为:

while steps < self.config.max_steps:
    ...

达到上限后返回结构化状态,而不是继续消耗 API:

{
  "status": "max_steps",
  "answer": "达到最大执行轮次,任务尚未通过外部验证",
  "steps": 10
}

故障点②:Context 无限膨胀

故障代码

# 每轮都追加 Assistant 和 Tool 消息,但从不清理历史。
messages.append(assistant_message)
messages.append(tool_message)

Harness 解决方式

Agent 每次调用模型前都会执行上下文压缩:

messages = compress_messages(
    messages,
    config.context_token_limit,
    config.keep_recent_blocks,
)

也可以单独使用上下文模块:

from harness_agent.context import compress_messages, estimate_tokens

before_tokens = estimate_tokens(messages)
compressed_messages = compress_messages(
    messages,
    token_limit=12_000,
    keep_recent_blocks=6,
)
after_tokens = estimate_tokens(compressed_messages)

print("压缩前:", before_tokens)
print("压缩后:", after_tokens)

压缩过程会固定保留:

System Prompt
原始 User Goal
较早历史摘要
最近若干完整交互消息块

Assistant Tool Calls 和对应 Tool Results 会被视为一个不可拆分的消息块:

[
    {
        "role": "assistant",
        "tool_calls": [
            {
                "id": "call-1",
                "function": {
                    "name": "read_file",
                    "arguments": "{\"path\": \"demo.py\"}",
                },
            }
        ],
    },
    {
        "role": "tool",
        "tool_call_id": "call-1",
        "content": "...",
    },
]

因此不会因为压缩而产生缺失 tool_call_id 的协议错误。


故障点③:System Prompt 漂移

故障代码

while True:
    # 每轮修改 System Prompt,使模型行为基线不断变化。
    messages[0]["content"] = (
        f"你是编程助手。[session_iter={iteration}]"
    )

Harness 解决方式

System Prompt 只在创建 Agent 时设置一次:

FIXED_SYSTEM_PROMPT = """你是一个严谨的编程智能体。
只能在配置的工作区中操作。
必须通过外部验证后才能声称任务完成。"""

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
    system_prompt=FIXED_SYSTEM_PROMPT,
)

result = agent.run("修复测试")

主循环只读取 self.system_prompt,不会在每轮修改它。恢复 Checkpoint 时还会校验:

if (
    state.get("task") != task
    or state.get("system_prompt") != self.system_prompt
):
    raise ValueError("checkpoint 与当前任务或系统提示词不匹配")

这能避免使用其他任务或其他 Prompt 的旧状态继续运行。


故障点④:Tool 错误被吞没

故障代码

try:
    result = tool(**arguments)
except Exception:
    # 模型无法区分空文件、权限错误和工具异常。
    result = ""

Harness 解决方式

所有工具统一返回 ToolResult

from harness_agent import create_workspace_tools

registry = create_workspace_tools(config.workspace)
result = registry.execute(
    "read_file",
    {"path": "not-exists.txt"},
)

print(result.ok)
print(result.error_type)
print(result.error)
print(result.to_json())

预期结果类似:

{
  "ok": false,
  "value": null,
  "error": "[Errno 2] No such file or directory: 'not-exists.txt'",
  "error_type": "FileNotFoundError"
}

模型可以根据 error_type 选择正确动作,例如:

  • FileNotFoundError:重新确认路径;
  • InvalidArguments:修正工具参数;
  • PermissionDenied:停止危险操作;
  • TimeoutExpired:缩小任务范围或调整策略。

工具名和参数也会在调用前进行校验:

unknown = registry.execute("unknown_tool", {})
wrong_args = registry.execute(
    "read_file",
    {"path": "README.md", "unexpected": True},
)

assert unknown.error_type == "UnknownTool"
assert wrong_args.error_type == "InvalidArguments"

故障点⑤:状态丢失,无法断点续跑

故障代码

messages = [
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": task},
]

# 进程退出后 messages、steps 和 token 全部丢失。
run_loop(messages)

Harness 解决方式

配置 Checkpoint 路径:

from pathlib import Path

config.checkpoint_path = (
    Path(config.workspace)
    / ".harness"
    / "checkpoint.json"
)

首次运行:

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
)
result = agent.run("完成一个多轮代码任务")

print(result["status"])

如果任务因为最大轮次、预算或异常而中断,Checkpoint 会保存:

任务描述
固定 System Prompt
完整 Messages
已执行轮次
输入和输出 Token
外部验证失败次数

恢复同一个任务:

resumed_result = agent.run(
    "完成一个多轮代码任务",
    resume=True,
)

print(resumed_result["steps"])
print(resumed_result["status"])

Checkpoint 使用临时文件和原子替换:

temporary_path.write_text(checkpoint_json, encoding="utf-8")
os.replace(temporary_path, checkpoint_path)

因此进程在写入期间退出时,不会轻易留下半个 JSON 文件。


故障点⑥:缺少参数校验、权限门禁和路径沙箱

故障代码

def write_file(path: str, content: str) -> str:
    # 模型可以传入任意绝对路径或 ../ 路径。
    with open(path, "w", encoding="utf-8") as file:
        file.write(content)
    return "done"

Harness 解决方式一:Workspace 路径沙箱

registry = create_workspace_tools(config.workspace)

escaped = registry.execute(
    "write_file",
    {
        "path": "../outside.txt",
        "content": "不允许写出工作区",
    },
)

assert escaped.ok is False
assert escaped.error_type == "PermissionError"

路径会先执行 resolve(),然后检查真实路径是否仍位于 Workspace 内:

candidate = (workspace / requested_path).resolve()
candidate.relative_to(workspace.resolve())

该检查同时覆盖:

  • ../ 路径逃逸;
  • 工作区外绝对路径;
  • 已存在符号链接指向工作区外部。

Harness 解决方式二:Permission Gate

hooks = HookManager()
gate = PermissionGate()
gate.register_to(hooks)

allowed, reason = hooks.trigger_gate(
    "pre_tool_use",
    "run_bash",
    {"command": "rm -rf /"},
)

assert allowed is False
print(reason)

将 Hooks 接入 Agent:

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
    hooks=hooks,
)

实际工具执行顺序为:

JSON 解析
→ pre_tool_use Gate
→ PermissionGate
→ 函数签名绑定
→ 基础类型校验
→ Workspace 路径检查
→ 实际执行

如果 Permission Handler 自身异常,Gate 会采用 Fail-Closed:

allowed = False
reason = "权限 Hook 执行失败"

故障点⑦:模型自行判断任务完成

故障代码

if not message.tool_calls:
    # 模型没有调用工具,不代表代码和测试真的正确。
    return message.content

Harness 解决方式

创建独立 pytest Verifier:

verifier = pytest_verifier(
    config.workspace,
    test_path="tests",
    timeout=60,
)

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
)
result = agent.run("修复所有失败测试")

完成判定变为:

flowchart LR
    A["模型声称完成"] --> B["执行独立 pytest"]
    B --> C{"pytest 是否通过?"}
    C -- 否 --> D["把失败输出反馈给模型"]
    D --> E["模型继续修复"]
    E --> B
    C -- 是 --> F["返回 completed"]
Loading

验证失败时,Harness 会追加新的 User 消息:

{
    "role": "user",
    "content": (
        "外部验证失败,请根据以下客观结果继续修复,"
        "验证通过前不要声称任务完成:..."
    ),
}

只有 Verifier 返回:

VerificationResult(
    passed=True,
    summary="pytest 验证通过",
    details={"exit_code": 0},
)

Agent 才返回:

{
  "status": "completed",
  "verification": {
    "passed": true,
    "summary": "pytest 验证通过"
  }
}

故障点⑧:Token 和费用成本失控

故障代码

while True:
    # 没有轮次、Token 或费用上限。
    response = client.chat.completions.create(...)

Harness 解决方式

配置 Token 硬上限:

config.max_steps = 20
config.max_total_tokens = 50_000

可选配置费用上限:

# 以下价格仅为配置示例,请按实际模型价格填写。
config.max_cost_usd = 0.50
config.input_cost_per_1k = 0.001
config.output_cost_per_1k = 0.002

Agent 在模型调用前检查预计输入:

budget.check_before_call(
    estimate_tokens(messages)
)

模型返回后再记录真实用量:

budget.record(
    input_tokens=response.usage.prompt_tokens,
    output_tokens=response.usage.completion_tokens,
)

如果供应商没有返回 usage,Harness 仍会执行本地估算,不能借此绕过预算。

超出预算时不会继续调用模型:

result = agent.run("执行受预算控制的任务")

if result["status"] == "budget_exceeded":
    print(result["budget"])
    print(result["errors"])

返回示例:

{
  "status": "budget_exceeded",
  "budget": {
    "input_tokens": 42000,
    "output_tokens": 9000,
    "total_tokens": 51000,
    "cost_usd": 0.06,
    "max_total_tokens": 50000,
    "max_cost_usd": 0.5
  }
}

子 Agent 同样可以设置独立限制:

from harness_agent import delegate

subtask_result = delegate(
    "分析测试覆盖缺口",
    config=config,
    tools=tools,
    allowed_tools=["read_file"],
    max_steps=5,
    max_total_tokens=10_000,
    max_depth=2,
)

4.2 八大故障治理完整组合示例

下面的代码将循环、上下文、工具、安全、进度、验证、预算和恢复机制组合到同一个 Agent 中:

from harness_agent import (
    HarnessAgent,
    HarnessConfig,
    HookManager,
    PermissionGate,
    PlanTracker,
    ProgressTracker,
    create_workspace_tools,
    pytest_verifier,
)

# 1. 加载并设置硬限制
config = HarnessConfig.from_env()
config.max_steps = 20
config.max_total_tokens = 100_000
config.context_token_limit = 12_000
config.keep_recent_blocks = 6
config.verification_attempts = 2

# 2. 创建 Workspace 沙箱工具
tools = create_workspace_tools(
    config.workspace,
    timeout=config.tool_timeout_seconds,
)

# 3. 注册会话级 Feature List
planner = PlanTracker()
tools.register(
    planner.tool,
    name="todo",
    description="查询、更新或清空当前任务计划",
)

# 4. 创建生命周期 Hooks
hooks = HookManager()

# 5. 注册权限门禁
permission_gate = PermissionGate()
permission_gate.register_to(hooks)

# 6. 注册结构化进度日志
progress = ProgressTracker(
    config.workspace / ".harness" / "progress.jsonl"
)
progress.register_to(hooks)

# 7. 创建独立 pytest 验证器
verifier = pytest_verifier(
    config.workspace,
    test_path="tests",
    timeout=config.tool_timeout_seconds,
)

# 8. 创建并运行 Harness Agent
agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
    hooks=hooks,
)

result = agent.run(
    "先制定计划,再检查当前工程测试;如果失败则修复,"
    "直到外部 pytest 验证通过。",
    resume=False,
)

print("状态:", result["status"])
print("轮次:", result["steps"])
print("预算:", result["budget"])
print("验证:", result["verification"])
print("错误:", result["errors"])

如果任务之前中断,可以使用完全相同的任务描述恢复:

result = agent.run(
    "先制定计划,再检查当前工程测试;如果失败则修复,"
    "直到外部 pytest 验证通过。",
    resume=True,
)

该组合对应关系如下:

故障点 生效机制 关键配置或对象
① 循环失控 Agent Loop config.max_steps
② Context 溢出 Context Management context_token_limitkeep_recent_blocks
③ Prompt 漂移 Stable System Prompt HarnessAgent.system_prompt
④ Tool 错误吞没 Tool Use ToolRegistryToolResult
⑤ 状态丢失 Checkpoint Recovery、Progress checkpoint_pathProgressTracker
⑥ 权限缺口 Permission Gate、Workspace Sandbox PermissionGatecreate_workspace_tools
⑦ 模型自判完成 Verification Loop pytest_verifier
⑧ 成本失控 Token Budget max_total_tokensmax_cost_usd

5. Agent 主循环

flowchart TD
    Start["启动任务"] --> Init["初始化消息、预算和错误状态"]
    Init --> Resume{"resume=True 且存在 Checkpoint?"}
    Resume -- 是 --> Load["恢复 Messages、轮次、预算和验证次数"]
    Resume -- 否 --> Session["触发 session_start"]
    Load --> Session

    Session --> Limit{"steps < max_steps?"}
    Limit -- 否 --> Max["返回 max_steps"]
    Limit -- 是 --> Pre["触发 pre_iteration"]
    Pre --> Compress["压缩过长上下文"]
    Compress --> PreBudget["调用前 Token 预算检查"]
    PreBudget --> LLM["调用大模型"]
    LLM --> Usage["记录真实或估算 Token"]
    Usage --> ToolDecision{"存在 Tool Calls?"}

    ToolDecision -- 是 --> Parse["解析 JSON 参数"]
    Parse --> Gate["触发 pre_tool_use Gate"]
    Gate --> Allowed{"权限是否放行?"}
    Allowed -- 否 --> Denied["生成 PermissionDenied"]
    Allowed -- 是 --> Execute["签名校验并执行工具"]
    Denied --> ToolResult["结构化 ToolResult 回写"]
    Execute --> ToolResult
    ToolResult --> Progress["触发 post_tool_use"]
    Progress --> Save["原子保存 Checkpoint"]
    Save --> Limit

    ToolDecision -- 否 --> Verify{"配置了 Verifier?"}
    Verify -- 否 --> Complete["返回 completed"]
    Verify -- 是 --> External["执行独立外部验证"]
    External --> Passed{"验证通过?"}
    Passed -- 是 --> Clear["清理 Checkpoint"]
    Clear --> Complete
    Passed -- 否 --> Attempts{"超过验证重试次数?"}
    Attempts -- 是 --> Failed["返回 verification_failed"]
    Attempts -- 否 --> Feedback["把客观失败结果反馈给模型"]
    Feedback --> Save
Loading

终止状态

状态 含义 是否保留 Checkpoint
completed 模型完成且外部验证通过,或配置允许不验证 验证成功时清理
max_steps 达到最大执行轮次
budget_exceeded Token 或费用超过硬预算
verification_failed 达到外部验证失败次数上限
error 未预期异常

6. 工具调用与安全边界

flowchart LR
    Call["模型 Tool Call"] --> Json["JSON 解析"]
    Json --> Hook["pre_tool_use Hook"]
    Hook --> Permission["Permission Gate"]
    Permission --> Signature["函数签名绑定"]
    Signature --> Type["基础类型检查"]
    Type --> Sandbox["Workspace 路径沙箱"]
    Sandbox --> Run["执行工具"]
    Run --> Result["ToolResult JSON"]
    Result --> Model["结果回写模型"]
Loading

ToolResult 结构

无论工具成功还是失败,都会返回统一结构:

{
  "ok": false,
  "value": null,
  "error": "文件不存在",
  "error_type": "FileNotFoundError"
}

这使模型可以区分:

  • 工具成功;
  • 参数不合法;
  • 工具不存在;
  • 权限被拒绝;
  • 文件不存在;
  • 运行超时;
  • 其他明确异常。

Workspace 内置工具

create_workspace_tools() 当前提供:

工具 功能 安全措施
read_file 读取 UTF-8 文本文件 Workspace 边界和真实路径检查
write_file 原子写入 UTF-8 文本文件 Workspace 边界、临时文件、os.replace
run_pytest 执行 pytest Workspace cwd、超时、输出截断、当前解释器

文件路径在 resolve() 后再次检查,因此能够拦截:

  • ../ 路径逃逸;
  • 工作区外绝对路径;
  • 已存在符号链接指向工作区外部;
  • 模型构造的路径绕过尝试。

当前没有内置 Shell 工具。如果后续增加命令执行能力,不应直接使用 shell=True,应继续增加 argv 白名单、环境变量过滤和人工审批策略。


7. Hooks 生命周期

HookManager 用于把权限、进度、指标和审计从主循环解耦。

Hook 事件 触发时机 典型用途
session_start 会话初始化或恢复后 Session 日志、Tracing
pre_iteration 每轮模型调用前 轮次指标、取消检查
post_iteration 模型响应和 Token 统计完成后 Token、费用和延迟统计
pre_tool_use 工具执行前 Permission Gate、审批
post_tool_use 工具执行后 工具审计、结果记录
verification 外部验证完成后 测试结果和质量指标
checkpoint_saved Checkpoint 落盘后 恢复点审计
session_stop 任意终止状态返回前 最终状态、错误和预算汇总

普通 Hook 异常会被隔离,不会中断其他 Handler;pre_tool_use 是安全 Gate,Handler 异常时采用 Fail-Closed,默认拒绝工具执行。


8. Progress 与 Checkpoint

这两个机制职责不同,不能互相替代。

机制 文件 面向对象 保存内容 主要用途
Progress Tracking .harness/progress.jsonl 人类和分析程序 生命周期事件、工具、验证、终止状态 审计、观察、统计
Checkpoint Recovery .harness/checkpoint.json Agent 恢复逻辑 Messages、轮次、预算、验证失败次数 中断恢复、避免重放

Checkpoint 保存内容

{
  "version": 1,
  "task": "用户任务",
  "system_prompt": "固定系统提示词",
  "messages": [],
  "steps": 3,
  "budget": {
    "input_tokens": 1000,
    "output_tokens": 300,
    "total_tokens": 1300
  },
  "verification_failures": 1
}

Checkpoint 使用临时文件和 os.replace() 原子替换,避免程序在写入中途退出后留下残缺 JSON。

恢复执行:

result = agent.run(
    "继续原来的任务",
    resume=True,
)

恢复时会校验任务和 System Prompt,防止把其他任务的快照错误加载到当前会话。


9. 环境要求与安装

Python 版本

Python >= 3.13

项目依赖

依赖 用途
openai 调用 DeepSeek/OpenAI-compatible Chat Completions
python-dotenv .env 加载配置
pytest 自动化测试和外部验证

使用 uv 安装

cd /Users/jean/PycharmProjects/agent-evolve
uv sync

如果虚拟环境已创建,可直接使用:

.venv/bin/python --version
.venv/bin/pytest --version

10. 环境变量

在工程根目录创建或修改 .env

DEEPSEEK_API_KEY=your_api_key
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL_NAME=deepseek-chat

# 以下为可选 Harness 配置
HARNESS_WORKSPACE=/Users/jean/PycharmProjects/agent-evolve
HARNESS_MAX_STEPS=20
HARNESS_MAX_TOTAL_TOKENS=100000
HARNESS_CONTEXT_TOKEN_LIMIT=12000
HARNESS_CHECKPOINT_PATH=/Users/jean/PycharmProjects/agent-evolve/.harness/checkpoint.json

HarnessConfig 配置项

配置项 默认值 说明
model_name deepseek-chat 模型名称
workspace 当前目录 文件和测试工具允许操作的根目录
max_steps 20 Agent 最大循环轮次
max_total_tokens 100000 单 Session 累计 Token 上限
max_cost_usd None 可选美元费用上限
input_cost_per_1k 0.0 每千输入 Token 费用
output_cost_per_1k 0.0 每千输出 Token 费用
context_token_limit 12000 触发上下文压缩的估算阈值
keep_recent_blocks 6 压缩时保留的近期完整消息块数量
tool_timeout_seconds 60 工具和 pytest 超时
verification_attempts 2 外部验证失败重试次数
require_verification True 是否强制要求 Verifier
checkpoint_path .harness/checkpoint.json Checkpoint 路径
temperature 0.0 主 Agent 模型温度

input_cost_per_1koutput_cost_per_1k 不会自动读取供应商实时价格,需要根据实际模型自行配置。


11. 快速开始

运行完整演示

cd /Users/jean/PycharmProjects/agent-evolve
.venv/bin/python demo.py

demo.py 已接入:

  • Workspace Tools;
  • PlanTracker;
  • HookManager;
  • PermissionGate;
  • ProgressTracker;
  • pytest Verifier;
  • Token Budget;
  • Checkpoint Recovery。

最小类式调用

from harness_agent import (
    HarnessAgent,
    HarnessConfig,
    create_workspace_tools,
    pytest_verifier,
)

config = HarnessConfig.from_env()
tools = create_workspace_tools(
    config.workspace,
    timeout=config.tool_timeout_seconds,
)
verifier = pytest_verifier(
    config.workspace,
    "tests",
    timeout=config.tool_timeout_seconds,
)

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=verifier,
)

result = agent.run("检查并修复测试,直到 pytest 通过")
print(result["status"])
print(result["answer"])

函数式兼容调用

core.py 仅提供函数式入口,内部仍然复用 HarnessAgent

from harness_agent import HarnessConfig, create_workspace_tools, run_agent

config = HarnessConfig.from_env()
config.require_verification = False
tools = create_workspace_tools(config.workspace)

result = run_agent(
    "分析当前工程",
    tools,
    config,
    verifier=None,
)

如果不传 Verifier,必须在创建 Agent 前显式设置 config.require_verification = False


12. 接入 Planner、Permission 和 Progress

from harness_agent import (
    HarnessAgent,
    HarnessConfig,
    HookManager,
    PermissionGate,
    PlanTracker,
    ProgressTracker,
    create_workspace_tools,
    pytest_verifier,
)

config = HarnessConfig.from_env()
tools = create_workspace_tools(config.workspace)

# 会话级计划工具
planner = PlanTracker()
tools.register(
    planner.tool,
    name="todo",
    description="查询、更新或清空当前任务计划",
)

# 生命周期扩展
hooks = HookManager()
PermissionGate().register_to(hooks)
ProgressTracker(
    config.workspace / ".harness" / "progress.jsonl"
).register_to(hooks)

agent = HarnessAgent(
    config=config,
    tools=tools,
    verifier=pytest_verifier(config.workspace, "tests"),
    hooks=hooks,
)

result = agent.run("先制定计划,再检查并修复测试")

13. Feature List

PlanTracker 是会话级计划管理器,不使用全局 TODOS

注册为工具

planner = PlanTracker()
tools.register(planner.tool, name="todo")

更新计划

planner.tool(
    action="update",
    items=[
        {
            "id": "1",
            "content": "检查测试失败原因",
            "status": "in_progress",
        },
        {
            "id": "2",
            "content": "修改实现并重新验证",
            "status": "pending",
        },
    ],
)

合并状态

planner.tool(
    action="update",
    merge=True,
    items=[
        {
            "id": "1",
            "content": "检查测试失败原因",
            "status": "completed",
        }
    ],
)

有效状态:

pending
in_progress
completed
blocked

14. Generator-Evaluator

from harness_agent import HarnessConfig, evaluate

config = HarnessConfig.from_env()

result = evaluate(
    candidates=[
        "方案 A:直接修改原函数",
        "方案 B:提取独立服务并增加测试",
    ],
    rubric="正确性、可维护性、安全性和改动范围",
    config=config,
)

print(result["best_index"])
print(result["scores"])
print(result["reasoning"])

返回结构:

{
  "best_index": 1,
  "scores": [0.5, 0.9],
  "reasoning": "第二个方案具备更好的测试边界",
  "error": null
}

Evaluator 会校验:

  • 分数数量是否与候选数量一致;
  • 分数是否位于 01
  • best_index 是否越界;
  • 返回内容是否为合法 JSON 对象。

解析失败时会显式设置 error,不会把回退结果伪装成可靠评分。


15. Subagents

from harness_agent import HarnessConfig, create_workspace_tools, delegate

config = HarnessConfig.from_env()
tools = create_workspace_tools(config.workspace)

result = delegate(
    "只读分析 tests 目录中的测试覆盖缺口",
    config=config,
    tools=tools,
    context="父任务正在评估 Harness 测试完整性",
    allowed_tools=["read_file"],
    max_steps=5,
    max_total_tokens=10000,
    max_depth=2,
)

子 Agent 具备:

  • 独立消息历史;
  • 独立 System Prompt;
  • 工具白名单;
  • 最大轮次限制;
  • Token 硬上限;
  • 递归深度限制;
  • 不覆盖父 Agent Checkpoint。

当前子 Agent 使用独立预算上限,但尚未自动聚合到父 Agent 的 BudgetGuard,详见“当前限制”。


16. 测试

运行全部测试

cd /Users/jean/PycharmProjects/agent-evolve
.venv/bin/pytest -q -p no:cacheprovider

当前测试结果:

19 passed

测试覆盖

测试文件 覆盖内容
test_harness_agent.py 最大轮次、上下文压缩、Prompt 稳定、结构化错误、Checkpoint、沙箱、Verifier、Budget、Tool Schema
test_harness_extensions.py Hooks、Permission Gate、Progress、Planner、Evaluator、Subagent、core 兼容入口
test_verifier_real.py 使用当前 Python 解释器执行真实 pytest 子进程

语法检查

.venv/bin/python -m compileall -q demo.py harness_agent tests

17. 当前实现与参考示例的主要差异

维度 参考示例 当前工程
主循环 core.py 中的大函数 agent.py 中的 HarnessAgentcore.py 仅兼容
上下文压缩 模块存在,但未接入主循环 每轮模型调用前强制接入
完成验证 主要依赖 Verification Prompt 独立 Verifier 是完成门禁
pytest 解释器 硬编码 python 使用 sys.executable
Progress Markdown 人类日志 结构化 JSONL 事件
恢复能力 没有真正的会话恢复 原子 JSON Checkpoint 和 resume=True
Planner 模块全局 TODOS 实例级 PlanTracker
Permission 命令正则,需要人工接线 Hook Gate + 命令策略 + ToolRegistry + Workspace 沙箱
Evaluator 自己创建 Client,解析失败默认均分 Client 可注入、结果严格校验、显式错误
Subagent 直接递归调用主函数 独立上下文、工具白名单、深度和预算限制
Budget usage 缺失时可能绕过 usage 缺失时也会估算并计入

18. 当前限制

当前版本已经覆盖基础 Harness 和主要扩展机制,但仍有以下边界:

  1. 没有自动重试策略:429、网络中断和部分 5xx 尚未进行有限重试。
  2. Token 为启发式估算:模型未返回 usage 时使用字符数除以 4,不等同于精确 tokenizer。
  3. 价格不会自动更新:美元费用依赖手动配置的千 Token 单价。
  4. Planner 未自动写入 Agent CheckpointPlanTracker 提供 snapshot()restore(),但尚未注册为通用状态提供者。
  5. 子 Agent 预算未聚合到父预算:子 Agent 有独立硬限制,但父子总成本尚未统一汇总。
  6. Hooks 为同步执行:长耗时 Hook 会增加主循环延迟。
  7. 没有内置 Shell 工具:当前 PermissionGate 已准备命令策略,但工程默认不暴露命令执行能力。
  8. 没有文件变更回滚:原子写入避免半文件,但验证失败时不会自动恢复旧版本。
  9. 没有并发 Subagent 调度器:当前 delegate() 为同步、单子任务执行。
  10. 没有真实 API 自动化测试:测试默认使用 FakeClient,避免产生外部费用。

19. 后续演进建议

flowchart LR
    A["当前 Harness"] --> B["模型调用 Retry Policy"]
    B --> C["统一 Model Gateway"]
    C --> D["Planner 状态接入 Checkpoint"]
    D --> E["父子 Agent 共享总预算"]
    E --> F["文件 Diff 与失败回滚"]
    F --> G["并发 Subagent 调度"]
    G --> H["Tracing 与指标面板"]
Loading

建议优先级:

  1. 增加带指数退避和 jitter 的有限重试;
  2. 将所有模型调用统一封装为 Model Gateway;
  3. 建立可注册的 Session State Provider;
  4. 将 Planner 和其他扩展状态纳入 Checkpoint;
  5. 建立父子 Agent 共享预算和取消信号;
  6. 增加文件 Diff、变更集和失败回滚;
  7. 加入并发 Subagent 上限和任务调度;
  8. 接入 OpenTelemetry 或结构化指标系统。

20. 相关文档


21. 安全提示

该项目当前用于智能体工程探索和教学验证。即使已经加入 Workspace 沙箱、Permission Gate、预算和验证器,也不应在未经额外审计的情况下直接赋予智能体:

  • 生产服务器权限;
  • 云平台管理员凭据;
  • 数据库写权限;
  • 无限制 Shell;
  • 工作区之外的文件权限;
  • 无上限 API 额度。

在生产场景中,应继续加入人工审批、最小权限凭据、隔离容器、网络出口控制、审计存储和事故回滚机制。

About

Agent演进示例代码

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages