大数跨境

实测 17 种 Agentic Loop Engineering 技术:如何构建更可靠的 AI Agents

实测 17 种 Agentic Loop Engineering 技术:如何构建更可靠的 AI Agents AI大模型观察站
2026-07-07
14
导读:本文实测 17 种 Agentic Loop Engineering 技术,涵盖反馈循环、验证器、RAG、工具调用、CI/PR 自动化与安全护栏。


Loop engineering 是让 agentic systems 真正变得可靠(而不只是单次 demo 看起来惊艳)的**最新且传播最快的方法**。它不是手工给一个 agent 写 prompt,而是围绕它设计 loop:一个递归系统,用来发现工作、把工作交给 agent、验证结果,并决定下一步该做什么,直到目标达成。一个典型的 loop 包含如下组件:

loop engineering 的组件包括:

  1. The PR babysitter:照看一个失败的 pull request,让它从红变绿,并且只有在真实测试通过后才标记为 ready。

  2. The CI sweeper:区分真实回归和 flaky test,因此绝不会把修复预算浪费在 flake 上。

  3. The maker and checker:第二个 agent,通过运行自己的测试而不是相信某个意见,抓住 writer 确信正确但实际上错误的代码。

  4. Daily triage:从海量常规报告中浮现出少数紧急 bug。

  5. Memory and retrieval:给 loop 一条读取路径,让它访问存在于模型权重之外的事实。

以及更多……

以上只是最有趣的五个,但总共有十七个组件,从低层 loop primitives 到这些生产模式。我开发了每一个组件,并在不同的真实数据集上测试它,看看它在真实问题上到底能多大程度增强一个 agentic system。

在这篇博客中,我们会逐个拆解每个组件背后的数学,实现它,并测试它,在每一步打印输出,这样你无需运行任何东西也能跟上。

所有代码都在 GitHub repository 中:

https://github.com/FareedKhan-dev/agentic-loop-engineering-course

目录

  • 什么是 loop engineering,以及 loop 的结构

  • 设置项目,共享库

  • 引擎,以及一个可以信任的 scorer

  • loop:run until done

  • Skills 与 context engineering

  • Sub-agents:maker 与 checker

  • Memory 与带 retrieval 的 state

  • Worktrees,并行隔离

  • Connectors 与 tools

  • 协调多个 loops

  • Budget、cost 与 observability

  • Safety 与 guardrails

  • Pattern:daily triage

  • Pattern:duplicate detection

  • Pattern:CI sweeper,先分类再修复

  • Pattern:PR babysitter

  • Pattern:dependency sweeper,按风险路由

  • Pattern:post-merge cleanup,发现 tech debt

  • Pattern:changelog drafter

  • Capstone:组合这支 orchestra

  • 这些数字到底告诉我们什么

  • 下一步往哪里走

什么是 loop engineering,以及 loop 的结构

杠杆点转移到 loops 并不是偶然的,它是分阶段演进到这里的。

  • Prompt engineering:写一个好的 prompt,然后阅读回复。

  • Context engineering:为单轮对话组装正确的文件、文档和示例。

  • Harness engineering:设计一个 agent 运行的环境,包括它的工具、权限和规则。

  • Loop engineering:设计递归系统:发现工作,把工作交给 agent,验证结果,持久化学到的内容,并反复决定下一步行动,直到目标达成。

这一切背后的单一核心思想是:一个 loop 的质量,只取决于它所连接的可验证信号的质量。 一个只是让 agent 根据自己意见重新运行的 loop 几乎不会改进;但一个连接到真实检查、测试、schema、检索事实或 evaluation harness 的 loop,会产生可衡量的提升。

本文中的每个组件都是同一张图里的一个盒子。我们会一次构建一个盒子,然后在 capstone 中把它们重新组合,并用真实 evaluation harness 验证结果。

工作被调度,triage skill 决定什么重要,state and memory 把 agent 需要知道的内容交给它,worktree 隔离变更,implementer 编写代码,verifier 用真实依据检查它,connectors 让它接触工具,而 gate 决定是执行还是升级给人。

本系列的其余部分会隔离这些盒子中的每一个,并衡量它的贡献。

三条规则塑造了每一次测量,也正是它们让这些数字值得信任。

  1. 在把 loop 连接到某个信号之前,先验证这个信号。 loop 是一个反馈系统,它的质量只取决于它测量的东西。如果 scorer 默默放过错误代码,那么每一次改进都是虚构的,所以我们做的第一件事就是证明 scorer。

  2. baseline 必须先真正失败。 每个组件都在它的 failure mode 真正造成伤害的地方进行测量,因此它弥合的差距是真实的,而不是制造出来的。

  3. 报告 nulls。 当一种技术没有带来任何提升,或者饱和,或者用一个指标换另一个指标时,我会保留这个结果并解释它。一个只展示胜利的系列,会让人对 loops 何时有帮助产生错误理解。

测量中还有一个刻意选择。我为每个组件使用不同的数据集,因为单一 benchmark 会让某个组件因为错误原因看起来很好

所以 run-until-done 在浅层、可修复的代码上测量;context 在 text-to-SQL 上测量,因为缺失 schema 会致命;retrieval 在存在于模型外部的事实上测量;isolation 在真实并发 git 写入上测量;triage 和 deduplication 在真实 bug tracker 上测量。

每个组件都在它的 failure mode 真正造成伤害的地方测量,而 scorer 也随任务改变,因为 pass@1 对 retrieval 或 classification 问题来说是错误的标尺。

设置项目,共享库

在任何组件之前,有一个 setup 选择让之后所有数字都可比较。

每个 section 都导入同一个小型 library,因此测得的差异只能来自被测试的 loop component,而不是不同的 model call 或不同的 scorer。它的结构如下:

from common import data            # dataset loaders, all normalized to one Problem type
from common import agents          # single_shot, grade, and the maker/checker building blocks
from common import loops           # self_refine, the run-until-done loop
from common import eval as ev      # the trustworthy scorer, verified in the next section
from common import memory          # the embedding store used by memory and the patterns
from common import tools           # the python tool and a ReAct solver
from common import show, plotting, runlog   # printing, charts, and results persistence

现在值得抽出两个部分,因为后面所有内容都依赖它们。eval 中的 scorer 是判断代码是否正确的真实来源,并且它携带了后续 patterns 会复用的小而精确的 metric helpers。

def prf1(tp, fp, fn) -> dict:
    """Precision, recall, and F1 from raw counts. Used by triage and tech-debt detection."""
    precision = tp / (tp + fp) if (tp + fp) else 0.0
    recall    = tp / (tp + fn) if (tp + fn) else 0.0
    f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0.0
    return {"precision": precision, "recall": recall, "f1": f1, "tp": tp, "fp": fp, "fn": fn}


def recall_at_k(ranked_ids, relevant_ids, k) -> float:
    """Fraction of the relevant items that land in the top k. Used by duplicate detection."""
    if not relevant_ids:
        return 0.0
    return len(set(ranked_ids[:k]) & relevant_ids) / len(relevant_ids)

每个代码数据集都会规范化成同一种形状,因此同一个 single_shot 和 grade 可以在每个 benchmark 上工作,而不需要特殊分支。

@dataclass
class Problem:
    id: str
    prompt: str
    entry_point: str = ""
    kind: str = "asserts"      # "asserts" (a function + assertions) or "io" (stdin/stdout)
    tests: Any = ""
    meta: dict = field(default_factory=dict)

每个代码 section 都依赖的两个 building blocks,是一次 generation call 和一次 grading call。

把它们放在共享库里意味着 single-attempt baseline、run-until-done loop、maker-checker split 和 capstone 都以完全相同的方式生成和评分,所以测得的差异反映的是组件本身,而不是我们悄悄改变了 prompt 或 score 的方式。

def single_shot(llm, problem, temperature=0.0, label="solve"):
    """Generate one solution attempt. Returns (code, generation)."""
    gen = llm.complete(solve_prompt(problem), system=SOLVE_SYSTEM,
                       temperature=temperature, label=label)
    return extract_code(gen.text), gen          # pull the fenced code block out of the reply


def grade(problem, code, timeout=10.0):
    """Grade a solution against its tests, the same eval used everywhere."""
    if getattr(problem, "kind", "asserts") == "io":
        return ev.check_io(code, problem.tests, timeout=timeout)    # stdin/stdout programs
    return ev.check_asserts(code, problem.tests, timeout=timeout)   # function + assertions

有了一个 library、一个 model、一个 scorer,并且它们在所有实验中共享,从这里开始我们测到的每个 delta 都是 loop component 在说话,而不是别的东西。

引擎,以及一个可以信任的 scorer

在测试任何一个 loop component 之前,我需要一个可以信任的 engine 和 scorer。

后面所有数字都是在同一个 model 和同一个 scorer 上比较一种 strategy 和另一种 strategy,所以如果其中任何一个不稳,后面的所有内容都没有意义。因此我必须先把它们固定下来。

所有内容都通过一个薄 client 与 model 通信。它把本地 server 包装在一个 OpenAI-compatible interface 后面,更重要的是,它记录每一次 call 的 token usage 和 latency。

这种共享 accounting 是后面 cost comparisons 公平的唯一原因。

class LLM:
    """OpenAI-compatible wrapper around the local server with usage tracking."""

    def chat(self, messages, temperature=0.0, max_tokens=1024, n=1,
             stop=None, seed=0, label="chat", retries=3):
        # every call is timed and its tokens are recorded under a label,
        # so the budget notebook can reconstruct the cost model from real data
        for attempt in range(retries):
            try:
                t0 = time.time()
                resp = self.client.chat.completions.create(
                    model=self.model, messages=messages, temperature=temperature,
                    max_tokens=max_tokens, n=n, stop=stop, seed=seed,
                )
                dt = time.time() - t0
                texts = [c.message.content or "" for c in resp.choices]
                self.usage.add(label, resp.usage.prompt_tokens,
                               resp.usage.completion_tokens, len(texts), dt)
                return Generation(texts=texts, seconds=dt, label=label)
            except Exception:                # network blips get a bounded retry
                time.sleep(1.5 * (attempt + 1))

当我们启动 engine 并询问它的 health 时,我们不只是在打开一个 socket。我们是在确认确切的 model、确切的 endpoint,以及一个真实 completion 能够返回。

#### OUTPUT ####
------------------------------------------- vLLM health --------------------------------------------
{
  "model": "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ",
  "base_url": "http://localhost:8000/v1",
  "reply": "ready"
}

=============================================== GPU ================================================
NVIDIA A100 80GB PCIe, 580.167.08, 81920 MiB, 72653 MiB
  torch:                     2.11.0+cu130

这把实验固定下来。一个 model,一个 quantization,一张 card,一个会统计每个 token 的 client。当后面的 notebook 展示 lift 时,它不可能是悄悄换了 model 或换了更快机器,因为所有这些都在这里冻结了。

现在是比 model 更重要的部分:scorer。整个系列中的每个 pass@1 数字都来自同一个 module,如果这个 module 错了,一切都错了。

所以在信任它之前,我们先喂给它已知正确和已知错误的 solutions,并 assert 它能正确评分,包括通过 timeout 捕获 infinite loop,而不是挂住。

def self_test() -> dict:
    """Run known-good and known-bad cases through every scorer and assert correctness."""
    results = {}

    # 1. a correct stdin/stdout program and a deliberately wrong one
    good = "import sys\na,b=map(int,sys.stdin.read().split())\nprint(a+b)"
    bad  = "import sys\na,b=map(int,sys.stdin.read().split())\nprint(a-b)"
    io_tests = [("2 3", "5"), ("10 4", "14")]
    assert check_io(good, io_tests)["all_pass"] is True     # good must pass
    assert check_io(bad,  io_tests)["all_pass"] is False    # bad must fail

    # 3. an infinite loop must be killed by the timeout, not hang the run
    assert check_io("while True:\n    pass", [("", "")], timeout=2.0)["all_pass"] is False
    results["timeout_caught"] = True

    # ... pass@k, similarity, sql execution match, and IR metrics each checked the same way ...
    results["all_passed"] = True
    return results

下面是它打印的内容。一个从不让任何东西失败的 checker 不是 checker,所以要看的那一行是 bad inputs 返回 false

#### OUTPUT ####
========================================= Scorer self-test =========================================
{
  "check_io":      { "good_all_pass": true,  "bad_all_pass": false },
  "check_asserts": { "good": true,           "bad": false },
  "timeout_caught": true,
  "pass_at_k":     { "n5_c1_k1": 0.2 },
  "similarity":    { "em": true, "edit": 0.75 },
  "sql_exec_match":{ "same": true, "diff": false },
  "ir_metrics":    true,
  "all_passed":    true
}

Scorer is trustworthy: known-good solutions pass, known-bad solutions fail.

每个 checker 都能区分。正确代码通过,错误代码失败,infinite loop 被捕获,而 pass@k estimator 对五个样本中一个正确样本返回了教科书式的 0.2

这个 pass@k 是 Chen et al. 的 unbiased estimator,也是大多数代码 notebooks 的标尺。

有了可信 scorer,我们就可以进行第一次测量。

但在完整运行之前,先在单个 problem 上观察整个 flow 会有帮助:我们发送的 prompt、model 返回的 code、以及我们计算出的 grade,因为这就是后面每个组件都会包装的 loop body。

p0 = problems[0]
prompt0 = agents.solve_prompt(p0)                 # compose the instruction sent to the model
code0, gen0 = agents.single_shot(llm, p0)         # one greedy attempt, extract the code block
res0 = agents.grade(p0, code0)                     # run it against the tests, exit-code truth
#### OUTPUT ####
------------------------------------- prompt sent to the model -------------------------------------
Implement the function described below. Return only one python code block with the complete
function plus any imports or helpers it needs.

Write a Python function `first_pair_summing(nums, target)` that returns a tuple (i, j) with
i < j of the first pair of indices whose values sum to target ...

-------------------------------------- model's extracted code --------------------------------------
  | def first_pair_summing(nums, target):
  |     num_to_index = {}
  |     for j, num in enumerate(nums):
  |         complement = target - num
  |         if complement in num_to_index:
  |             return (num_to_index[complement], j)
  |         num_to_index[num] = j
  |     return None

------------------------------------------ grading result ------------------------------------------
{ "all_pass": true, "stderr": "", "timed_out": false }
  tokens (prompt/completion): 118/69

这就是一次完整的数据流:prompt 进去,code 出来,然后拿到一个真实 grade。

baseline 只是把这个流程精确地跑完整个集合,不做 iteration,每个 problem 一次 greedy attempt,在一小组简单问题上证明 pipeline 能端到端运行。

#### OUTPUT ####
  PASS  smoke/two_sum_indices  (69 completion tokens)
  PASS  smoke/is_palindrome  (75 completion tokens)
  FAIL  smoke/run_length_encode  (126 completion tokens)
  PASS  smoke/fib  (91 completion tokens)
  PASS  smoke/io_sum  (85 completion tokens)

========================================= Baseline result ==========================================
  problems                         5
  solved                           4
  pass@1                           0.8000
  total tokens                     923
  wall time (s)                    7.0000

这个 single agent 解决了 5 个中的 4 个pass@1 为 0.80。这是参考线,而唯一失败的 run_length_encode 就是 loop 在这里可能改进的全部空间。

这个数字故意很高,因为这些问题很简单。

接近天花板的分数正是额外机制没有空间证明自己的区域,这也是后面每个 notebook 都转向 hard benchmark 的真正原因:在那里,一次 greedy attempt 经常失败,loop 才有真实 failure 可以处理。

engine 固定、scorer 证明之后,我们就可以开始添加 loop components,并信任测量结果。

loop:run until done

我从 loop 本身开始,也就是你能包在 single agent 外面的最简单东西:让它根据反馈再试,而不是回答一次就停止。

但这里有个很多人都会掉进去的陷阱,也是本节的重点。一个只是重新要求 model “try again” 的 loop 几乎没有帮助。

连接到真实 test output 的 loop 则完全不同。我们必须同时测量二者,才能看到差异。

核心是一个 function。它生成 solution、评分;如果失败,就把真实 error 反馈回去并请求修复,直到达到 attempt cap。

def self_refine(llm, problem, max_attempts=3, temperature=0.0, use_feedback=True):
    """Generate, test, fix, repeat until the tests pass or the attempt cap is hit."""
    history = []
    code, _ = agents.single_shot(llm, problem, temperature=temperature)   # attempt 1
    res = agents.grade(problem, code)
    history.append(bool(res["all_pass"]))
    attempt, solved_at = 1, (1 if res["all_pass"] else None)

    while not res["all_pass"] and attempt < max_attempts:
        attempt += 1
        if use_feedback:
            fb = _assert_feedback(problem, code)     # the real traceback / failing assertion
        else:
            fb = "Your previous solution was incorrect. Produce a different, correct solution."
        gen = llm.complete(_refine_prompt(problem, code, fb), ...)   # fix attempt
        code = agents.extract_code(gen.text)
        res = agents.grade(problem, code)
        history.append(bool(res["all_pass"]))
        if res["all_pass"] and solved_at is None:
            solved_at = attempt
    return {"solved": bool(res["all_pass"]), "solved_at": solved_at, "history": history}

反馈本身只是实际捕获到的 error。这里没有花活,我们运行 candidate 对抗 tests,然后把 interpreter 实际说的话交回去。

def _assert_feedback(problem, code, timeout=10.0):
    """Run the candidate against the tests and return the real error output."""
    res = ev.run_program(code + "\n\n" + problem.tests, timeout=timeout)
    if res.ok:
        return "All tests passed."
    err = (res.stderr or "").strip()
    return f"Running your function against the tests produced this error:\n{err[-700:]}"

fix prompt 随后把 task、previous attempt 和真实 feedback 拼接成一个 request,让 model 基于到底哪里出错来条件化,而不是再次猜测。

def _refine_prompt(problem, code, feedback):
    # the previous (failed) code plus the captured error, asking for a corrected version
    return (solve_prompt(problem)
            + "\n\nYour previous attempt failed. Test feedback:\n" + feedback
            + "\n\nFix it and return one corrected python code block.")

形式上,这个 loop 是一个很小的 update rule。每次 attempt 都是 model 在 previous code 和真实 feedback 条件下生成,我们在 grade 通过或达到 cap 时停止。

我们在 MBPP+ 的 60 个 problems 上运行它,在这里一次 greedy attempt 经常失败,因此有提升空间。首先加载 benchmark,看看 loop 正在处理什么。

#### OUTPUT ####
loaded MBPP+: 60 problems (seed 0), kind=asserts
each item is a natural-language function spec; the hidden assertions are never shown to the model

现在是三个条件:baseline single shot、带真实反馈的 loop、以及作为 control 的无反馈 loop。

#### OUTPUT ####
baseline pass@1                  0.7667     (46 of 60 on the first attempt)

--- loop WITH execution feedback ---
within 1 attempt(s)              0.7667
within 2 attempt(s)              0.8167
within 3 attempt(s)              0.8500
loop tokens                      21680

--- loop WITHOUT feedback (control) ---
within 1 attempt(s)              0.7667
within 2 attempt(s)              0.7667
within 3 attempt(s)              0.7833
loop tokens                      20675

看这两列。带真实 test feedback 的 loop 从 0.767 上升到 0.85,提升 8.3 points

同一个 loop 只给 “try again” 时只到 0.783,提升 1.7 points,token cost 几乎相同。有帮助的不是 retry,而是 signal。

这在真实代码上复现了 Huang et al. 的结果,也是关于 loops 最重要的一点。

曲线也表明 loop 并不是魔法。

提升集中在第二次 attempt,之后趋于平坦;像 mbppplus/306 这样的问题经过全部三次 attempts 仍然未解决,因为 loop 修的是浅层 bug,而不是错误算法。

这就是边界,也解释了为什么 attempt cap 很重要。

没有 cap 时,一个 loop 如果追逐一个它无法解决的问题,就会不断生成、不断失败、不断花费 tokens。因此 “run until done” 必须永远意味着 “run until done or until a budget says stop”。记住这一点,因为 budget section 会把它具体化。

Skills 与 context engineering

loop 能修复浅层 bug,但很多失败根本不是 bug,而是缺失知识。所以我接下来处理的是 context,在 loop 中我称之为 skills。当 model 缺少一块稳定 context 时,它不会停下来询问。

它会用自信的猜测填补空白,而这个猜测几乎总是错的。我把这称为 intent debt,而这是整个系列中最清晰的单一杠杆,所以我想仔细测量它。

最清晰的场景是 text-to-SQL。model 被要求写一个 query,但没有被告知 table schema,所以在 cold 条件下,它必须发明 table name 和 column names。

我们评分两件事:schema validity,它只问 query 中的每个 identifier 是否真的存在于 schema 中;以及 execution equivalence,它构造一个小 database,并检查 predicted query 返回的 rows 是否与 gold query 相同。

def schema_validity(pred_sql, create_sql) -> bool:
    """True only if every non-keyword identifier in the prediction exists in the schema."""
    valid = schema_names(create_sql)            # the real table and column names
    ids = pred_identifiers(pred_sql)            # identifiers the model actually used
    if not ids:
        return False
    return all(i in valid for i in ids)         # any invented name fails this


def exec_equiv(create_sql, pred_sql, gold_sql) -> bool:
    """Build a SQLite DB from the schema, insert synthetic rows, compare result sets."""
    path = build_db(create_sql)
    try:
        return bool(ev.sql_exec_match(path, pred_sql, gold_sql).get("match"))
    finally:
        os.remove(path)

我们加载 60 个 single-table questions,每个都带有自己的 schema 和 gold query。

#### OUTPUT ####
loaded sql-create-context: 60 questions (single-table)
sample: question = "what is the lowest lap with a qual of 145.926?"
        schema   = CREATE TABLE table_name_6 (laps INTEGER, qual TEXT, ...)

这是那个 question,先 cold 运行,再把 schema 作为 skill 注入。注意 table name 和 column name。

#### OUTPUT ####
--- cold (no schema) ---
SELECT MIN(lap) AS lowest_lap
FROM your_table_name
WHERE qual = 145.926
references only real columns?: False

--- schema injected as a skill ---
SELECT MIN(laps) FROM table_name_6 WHERE qual = '145.926'
references only real columns?: True

cold 时,model 发明了 your_table_name,并把 column 单数化成 lap。给定 schema 后,同一个 model 使用了真实的 table_name_6 和真实的 laps。现在对全部 60 个 questions 做同样比较。

#### OUTPUT ####
condition      schema_validity  exec_equiv
-------------  ---------------  ----------
cold           0.017            0.0
skill          1.0              0.95
skill_fewshot  1.0              0.95

这是整个系列中最显著的单项结果。

cold 条件下 0 percent executable,带 schema 时 95 percent executable。 60 个 questions 中没有任何一个 cold prediction 返回了正确 rows,因为 model 一直在猜名称。

一次 context injection 就把同一个 model 从猜测变成机械正确。注意我保留了这个 null result:在 schema 之上再添加两个 worked examples,结果仍然是相同的 95 percent。

一旦真正的 bottleneck 被移除,更多 context 就没有任何增益。所以教训是找到那块承重知识并注入它,而不是填充 prompt。

Sub-agents:maker 与 checker

这里有一个结构性事实,我花了一些时间才真正尊重它。

写代码的 agent 不能可靠地评判这段代码,因为它的 confidence 是由产生 bug 的同一套 reasoning 计算出来的。问它 “are you sure” 只是重新运行同一个盲点。

所以我们拆分角色:maker 负责生成,checker 的默认答案是 “reject”。然后我们必须测量每类 checker 实际上有多好。

我们关心的 checker 并不是读代码然后形成一个 opinion。它从 spec 编写自己的 tests 并运行它们,只有全部通过才 accept。

def selftest_verify(llm, problem, code, label="selftest"):
    """Checker writes its own tests and RUNS them, accept only if all pass (default REJECT)."""
    st = gen_self_tests(llm, problem, label=label)         # 4-6 asserts from the spec, no solution
    n_assert = sum(1 for ln in st.splitlines() if ln.strip().startswith("assert"))
    if n_assert == 0:
        return True, st                                    # degenerate: nothing to check
    return _count_passing(code, st) == n_assert, st        # accept only on a clean sweep

关键是 checker 只从 spec 写测试,从不看 hidden tests 或 implementer 的代码,因此它不会继承 maker 的盲点。

def gen_self_tests(llm, problem, label="gentests"):
    """The checker writes its own tests from the spec, without seeing the hidden tests."""
    prompt = ("Write 4 to 6 assert statements that any correct solution must satisfy. "
              "Use the exact function name. Do not implement the function, only the asserts.\n\n"
              + problem.prompt)
    g = llm.complete(prompt, system=_GEN_TESTS_SYSTEM, temperature=0.0, max_tokens=400, label=label)
    return extract_code(g.text)

在扩大规模之前,一个具体案例可以说明问题。在 mbppplus/644 上,implementer 确信自己的错误代码是正确的,只有 test-running checker 不同意。

#### OUTPUT ####
mbppplus/644   truth=0  self=1  judge=0  selftest=0

ground truth: passes tests?:                 False
implementer self-assessment says correct?:   True
test-running checker accepts?:               False

implementer 给坏代码开了证明。

现在扩展到全部 60 个 solutions,其中 13 个确实是错的,并比较四种 checkers 的 false-accept rate(checker 放行错误 solutions 的比例)以及其 accept pool 的 precision。

#### OUTPUT ####
checker        false_accept_rate  false_reject_rate  accept_precision
-------------  -----------------  -----------------  ----------------
trust_all      1.000              0.00               0.783
self_assess    0.769              0.00               0.825
llm_judge      0.538              0.17               0.848
selftest_run   0.308              0.34               0.886

从上到下读第一列。信任一切会接受 100 percent 的错误代码。

implementer 自评仍然接受 77 percent 的自家坏 solutions,因为盲点是共享的。

一个独立 model 审阅代码,在 54 percent 上好一些,但仍然是 opinion。

只有会编写并运行 tests 的 checker 将其降到 31 percent,并给出最干净的 accept pool,precision 为 88.6 percent

整篇文章的基本规则就在这张表里。一个 loop 必须用某种它无法靠说服绕过去的东西来验证。

这里我也保留一个 null result:best-of-N selection 没有增加任何东西,因为在 temperature 0.8 下,这个 32B model 对每个 problem 实际上近似确定性,所以没有 oracle gap 可供 selector 利用。

与其制造一个胜利,notebook 测量了 verifier discrimination,而这才是对 strong model 真正有回报的部分。

Memory 与带 retrieval 的 state

model 不可能知道存在于权重之外的事实,所以我想看看一点 memory 能把它带多远。项目约定、昨天做出的决策、上周测得的数字,根本不在 parameters 中。

如果一个 loop 要跨多个 sessions 运行,它需要一条进入 persistent state 的读取路径,并且需要在关键时刻取回正确事实。

retrieval 部分是一个小型 embedding store。它把 corpus embed 一次,然后对任意 query,通过 cosine similarity 返回最相似的 passages。这就是整个机制。

class EmbeddingMemory:
    def index(self, batch_size=64):
        # embed every stored doc once, normalized so the dot product IS cosine similarity
        self.emb = self.model.encode(self.docs, normalize_embeddings=True, batch_size=batch_size)
        self.emb = np.asarray(self.emb, dtype=np.float32)

    def retrieve(self, query, k=3, exclude_idx=None):
        """Return the top-k most similar docs as {index, score, doc, meta}."""
        if self.emb is None:
            self.index()
        sims = self.emb @ self._encode_query(query)      # cosine, because everything is normalized
        order = np.argsort(-sims)
        out = []
        for i in order:
            if exclude_idx is not None and int(i) == exclude_idx:
                continue
            out.append({"index": int(i), "score": float(sims[i]), "doc": self.docs[i]})
            if len(out) >= k:
                break
        return out

因为每个 vector 都经过 normalized,dot product 就直接是 cosine similarity,这也是整个 retrieval idea 依赖的 grounding signal。

构建 store 只需要两次调用:添加 passages 并 index 一次,之后每个 query 都是一次 retrieval。

mem = EmbeddingMemory()                  # BGE embeddings, the same store the patterns reuse
mem.add(project_passages)                # the persistent corpus of project facts
mem.index()                              # embed once, normalized so dot product is cosine
hits = mem.retrieve("what was the verifier false-accept rate?", k=3)   # top-3 grounding passages

为了测量这是否重要,我向 model 提了 14 个关于这个项目自身测量结果的问题,这类东西任何 model 都不可能从训练中知道。先 closed-book,再用 store 做 retrieval。

#### OUTPUT ####
closed-book      : 1 / 14 correct   (7.1%)
retrieval (RAG)  : 14 / 14 correct  (100.0%)
delta            : +92.9 points

这就是差距。

7 percent 到 100 percent。 model 在 closed-book 下答对的唯一问题是一个它能猜到的一般原则。我保留了这个 7 percent,而不是把它四舍五入为零,因为真正不可猜的是项目特定 facts。

External memory 是 loop 跨运行携带 knowledge 的方式,也是把 confident hallucination 变成 grounded answer 的方式。

不过实践中有一个 guard 很重要。

Retrieval 总是返回它的 top k,即使 store 中没有任何东西真正相关。因此 stale 或 off-topic passage 会像好 passage 一样自信地误导 model。

因为 retrieve 会给每个 hit 返回 similarity score,所以修复方法是 relevance threshold:丢弃低于阈值的 passage,并 fallback 到 closed-book answer,而不是用不相关内容 grounding answer。

进入 memory 的读取路径只有在它也能说 “nothing found” 时才安全。

Worktrees,并行隔离

我一开始尝试并行运行 agents,就遇到了一个残酷且无声的失败。

一旦你希望多个 agent 同时工作,并且其中几个同时编辑同一个文件,最后写入者获胜,其他人的工作就直接消失,而且完全没有 error。因此我开始测量这个问题有多糟,并修复它。

我让六个 agents 并发编辑一个共享 registry file,先使用一个 shared working tree,再使用隔离的 git worktrees 并加上 merge step。

#### OUTPUT ####
shared working tree : 1 / 6 agents' work survived   (16.7%)
git worktrees       : 6 / 6 agents' work survived   (100.0%)   [5 merge conflicts, all resolved]

在 shared tree 上,6 个 agents 的变更中只有 1 个幸存。另外五个被无声覆盖,这是最糟糕的 bug,因为没有任何东西告诉你它发生了。

使用隔离 worktrees 和真实 merge 后,全部六个都幸存,产生的五个 conflicts 被暴露并解决,而不是被静默丢弃。

修复方法不是聪明技巧,而是 isolation 加上 explicit integration step,这让 parallelism 安全到足以使用。

Connectors 与 tools

当 model 被要求基于具体数据回答精确问题时,它不会计算,而是猜测,并且数据越大,猜测越糟。

你可以看到它把真实 count 四舍五入成一个干净的 “about 10”。修复方法是把精确计算从 weights 中移出来,放进 loop 可以调用的 tool。

这里的 tool 是一个 Python executor,它代表任何真实 connector。

tool 本身很小。它在 subprocess 中运行一个 code block,并返回打印内容;如果失败,则返回 error。

def python_tool(code: str, timeout: float = 8.0) -> str:
    """Run a code block in a subprocess and return its stdout, or the error text."""
    r = ev.run_program(code, timeout=timeout)
    if r.timed_out:
        return "(timed out)"
    out, err = (r.stdout or "").strip(), (r.stderr or "").strip()
    return out if r.ok else f"(error) {err[-300:]}"

这个 tool 外面套着一个很小的 ReAct loop。model 输出 code block,tool 运行它,output 被反馈回去,然后 model 从真实结果中读取最终答案,而不是发明一个。

def react_solve(llm, question, max_steps=4, label="react"):
    """Run a short ReAct loop with the python tool. Returns the FINAL answer text."""
    transcript = f"Question: {question}\n"
    for _ in range(max_steps):
        gen = llm.complete(transcript, system=REACT_SYSTEM, temperature=0.0, max_tokens=400)
        text = gen.text or ""
        if "FINAL:" in text:                         # the model is done, read the answer
            return text.split("FINAL:", 1)[1].strip().splitlines()[0].strip()
        code = agents.extract_code(text)
        if code and ("print" in code or "=" in code):
            result = python_tool(code)               # actually run it, capture stdout
            transcript += text + f"\nTOOL OUTPUT:\n{result}\n"
        else:
            transcript += text + "\n(no runnable code, output a python block or a FINAL line)\n"
    ...

在 30 个生成的数据分析任务上,猜测和计算之间的差异非常明显。

#### OUTPUT ####
no-tool (guess from the data in the prompt) : 10.0%   (3 / 30)
with python tool (ReAct)                    : 83.3%   (25 / 30)
delta                                       : +73.3 points

10 percent 到 83 percent。 model 不是更擅长 arithmetic,它只是停止在脑子里做 arithmetic。没有 tool 时,它给出圆整、看似合理但错误的数字;有 tool 时,它计算真实答案并读回来。Connectors 让 loop 接触真实世界,而不是对它幻觉。

协调多个 loops

一个 loop 已经可用,所以下一个问题是多个 loop 同时运行会发生什么。真实系统不会只运行一个 loop,而是运行多个。一个 CI sweeper、一个 PR babysitter 和一个 dependency sweeper 同时 watch 同一个 repository 很正常。

问题在于,当它们的 work queues 重叠,并且它们在彼此不知情的情况下开始处理同一个文件时,就会做重复工作,并在上面燃烧 tokens。

我在一个 repo 上运行三个 concurrent loops,先不协调,再使用一个 shared registry 记录哪个 loop 已经在处理哪个 target,让其他 loop yield。

#### OUTPUT ####
uncoordinated : 5 collisions   ~1,000,000 tokens wasted on duplicate work
coordinated   : 0 collisions   (loops yield when a target is already owned)

没有 coordination 时五次 collisions,浪费大约 一百万 tokens;一旦每个 target 有一个 owner,降到 zero。wasted-token chart 把 cost 具体化了。

这是 worktrees section 中 single-owner idea 的提升版:从 files 提升到整个 loops。Coordination 不是免费的,但一次 registry lookup 比两个 loops 修复同一个 bug 两遍所花的一百万 tokens 便宜得多。

Budget、cost 与 observability

所有这些质量都有价格,我想精确知道它是多少。我运行的每个 loop 都在用 tokens 购买质量,而 tokens 就是真钱。因此一个 unattended loop 必须知道自己即将花费什么,并且拒绝超出预算。

这就是 research demo 和可托管运行系统之间的差别。

notebook 从记录的 token usage 重建每种 strategy 的 cost,而真正重要的 metric 是 cost per solved problem,因为一个提升质量的 strategy 只有在每次胜利的价格合理时才值得。

例如,run-until-done loop 的 cost 大约是 single shot 的 2.1 倍,这在前面我们已经直接看到了:同样 60 个 problems,21,680 tokens 对 10,504 tokens。

正确做法不是选择最便宜或最准的 strategy,而是找到每 token 购买最多质量的 operating point,这正是 cost-and-quality frontier 展示的内容。

frontier 才是重点。有些 strategies 以低成本购买大量质量,另一些花巨资只换来一两点提升。

像我们在 foundations section 构建的 engine 那样,在每次 call 上记录 token usage 的 loop,才让这张 chart 成为可能。也正因如此,本文每个组件的 lift 都有一个已知价格在旁边,而不是一个模糊的 “it helps”。

Safety 与 guardrails

最后一个 primitive 决定了这些东西是否能在我不盯着它的情况下运行。

当一个 unattended loop 产生变更时,它面临一个二元选择:无需人工直接 apply,或者 escalate 给人。无论哪边 gate 出错都很糟,所以我们测量它。

naive policy 是 “verifier passed, so apply it”。问题是 passing test 只覆盖你写了 tests 的路径,并不覆盖 irreversible action 或 risky change。

guardrail 是一个独立的 multi-axis decision:只有当 verifier 通过且 risk 低于阈值时才自动 act,否则 escalate 给 human。

我对 57 个 proposed actions 进行了评分,其中 38 个 genuinely risky,并比较两种 policies。

#### OUTPUT ####
                       naive (verifier only)   guardrail (verify + risk)
false auto-act rate    60.5%  (23 of 38)       0.0%   (0 of 38)
escalation recall      39.5%                    100.0%
escalation precision   n/a                      77.6%

naive policy 对 60.5 percent 的 risky changes 自动执行了,因为 verifier 是 green,它只检查这个。

guardrail 抓住了 每一个 risky action,把 false auto-acts 降到 zero,并把 escalation recall 提升到 100 percent,precision 为 77.6 percent。这意味着它也会 escalate 一些 safe changes,而对于 safety gate 来说,这是正确的错误方式。

这就是全部 primitives:Run until done、skills、maker and checker、memory、worktrees、connectors、coordination、budget 和 safety。测量完它们之后,就该把它们指向真实工作了,也就是一个真实 repository 会产生的 recurring jobs。

Pattern:daily triage

我指向的第一个真实工作是 daily triage。一个 repository 会产生大量 bug reports,而只有少数是 urgent。

loop 必须从噪声中浮现出少见的 Blocker,而我想回答的问题是 semantic model 是否比 keyword rule 做得更好。

我在一个真实 bug tracker 上测量它:来自 HBase project 的 5,395 条 reports,其中只有约 5 percent 是 high priority。

metric 是 recall,因为对 review queue 来说,漏掉 urgent bug 是昂贵失败。

首先看问题的形状,这也是它困难的原因。

#### OUTPUT ####
GitBugs hbase: 5395 reports loaded
high-priority (Blocker/Critical): 81 reports   <- the needle
everything else:                  5314 reports  <- the haystack

八十一个 urgent reports 藏在五千多个里面。classifier embed 每条 report,并按 urgency 排序;我们用 setup section 中相同的 prf1 helper 对 ranked queue 评分。

# the embedding classifier ranks reports by urgency; the keyword rule is the baseline
flagged = embedding_classifier.flag(reports)         # a set of report ids it calls urgent
m = prf1(tp=len(flagged & urgent),
         fp=len(flagged - urgent),
         fn=len(urgent - flagged))
print(f"recall {m['recall']:.3f}   F1 {m['f1']:.3f}")
#### OUTPUT ####
                 recall   F1
majority          0.000   0.000
keyword rule      0.346   0.156
embedding model   0.654   0.206

keyword rule 捕获 34.6 percent 的 urgent reports。embedding classifier 捕获 65.4 percent,在相同 review budget 下约为两倍。

由于 urgent reports 稀少且困难,所有方法的 F1 都很低,但这里重要的是 recall,而它几乎翻倍了。

Pattern:duplicate detection

接下来是 duplicate detection。一个 triage loop 必须识别新的 bug report 何时是旧 report 的 duplicate,即使措辞完全不同。

Lexical matching 看不出 “crash on startup” 和 “app dies when I open it” 是同一件事。Embeddings 可以。

我在 4,000 个 HBase issues 和 107 个已知 duplicate pairs 上测量它:对 candidates 排序,并查看 true match 是否进入 top results。

它使用的正是 memory section 中的 embedding store,对 candidates 排序,并用 recall_at_k 对 ranking 评分。

# rank candidates by embedding similarity, then ask if the true duplicate is in the top 10
mem = EmbeddingMemory(); mem.add(list(corpus.values())); mem.index()
ranked = [hit["index"] for hit in mem.retrieve(query_text, k=10)]
r10 = recall_at_k(ranked, relevant_ids, k=10)        # TF-IDF runs the same scoring, lower result
#### OUTPUT ####
method     recall@1   recall@10   MRR
TF-IDF       0.346       0.560     0.414
embeddings   0.495       0.654     0.546

Embeddings 将 Recall@10 从 0.56 提升到 0.65,将 mean reciprocal rank 从 0.41 提升到 0.55,因此 true duplicate 不仅更常出现,而且出现得更靠前。

这是 memory section 中完全相同的 retrieval read path,只是指向了不同的工作,这正是一次构建 primitives、多处复用的意义。

Pattern:CI sweeper,先分类再修复

红色 builds 是下一个问题,所以我构建了一个 CI sweeper。当 build 变红时,一半时间是真实 bug,另一半时间是 flaky test,它会自己恢复通过。

一个 naive sweeper 如果尝试修复所有东西,就会把大量努力浪费在 flakes 上,而 flakes 无法通过改代码修复。

loop engineering 的动作是先分类,只把 fix budget 花在真实 failures 上。

fix budget 是前面的 run-until-done loop,而 classifier 决定一个 red build 是否有资格进入它。

# classify first, only real regressions enter the expensive fix loop
for build in red_builds:
    if classify(build) == "flaky":
        quarantine(build)                                       # cannot be fixed by code, skip it
    else:
        loops.self_refine(llm, build.problem, use_feedback=True)   # the loop, spent only on reals
#### OUTPUT ####
20 red builds: 10 real regressions, 10 flaky tests
naive  "fix everything"  : 20 fix attempts   (10 wasted on flakes)
classify-first sweeper   : 10 fix attempts   (classifier accuracy 100%)
tokens saved             : ~2,000,000

classifier 在这个集合上是完美的,所以 sweeper 尝试了 10 次 fixes 而不是 20 次,每一次都用于真实 regression,并节省了大约 两百万 tokens,否则这些 tokens 会被用来 “fix” 根本没坏的 tests。

在昂贵 fix loop 前放一个廉价 triage step,是你能做的最高杠杆事情之一。

Pattern:PR babysitter

PR babysitter 建立在我已经证明的一点之上:你不能相信 implementer 自己的话。它把 pull request 从红色带过 repair,再到 ready-to-merge,而危险捷径是相信 “fix worked” 的声明。

我们已经在 maker-checker section 证明,implementer 对自己的工作大多数时候是错的,所以 merge-ready state 必须由真实 test run gate,绝不能由 self-report gate。

整个 pattern 就是一个 gate,它复用 foundations section 中的 grade,让 merge-ready state 只有在通过真实 tests 时才能到达。

# the "ready to merge" transition is gated on a real test run, never the implementer's word
def is_ready(pr) -> bool:
    res = agents.grade(pr.problem, pr.code)     # run the tests, exit-code truth
    return res["all_pass"]                       # ready only when it is actually green
#### OUTPUT ####
30 PRs, 6 red:
naive (trust the self-report) : false-ready 33.3%   (2 of 6 broken PRs marked ready)
verifier-gated                : false-ready 0.0%     (3 of 6 actually fixed and verified)

naive babysitter 根据对方自述,把 三分之一的 broken PRs 标记为 ready。verifier-gated 版本一个都没有,并且它真实修复并验证了六个中的三个。

这就是 foundations section 中 deterministic check 的唯一工作:成为能把工作推进到 merge-ready state 的唯一东西。

Pattern:dependency sweeper,按风险路由

dependency sweeper 是出错会真的造成伤害的地方。如果一个 unattended dependency bot 只要 green build 就 merge,那会令人担忧,因为 passing test suite 抓不住 major-version API break 或 malicious package。

loop 必须按 risk 路由 updates,只自动应用安全的 patch bumps,并 escalate 其余所有内容。

这是前面 safety router 的 dependency 专用版本,并且只是一条 routing rule。

# route each update by risk: auto-apply only safe patches, escalate everything else
def route(update) -> str:
    if update.bump == "patch" and not update.cve:
        return "auto-merge"                       # safe and reversible
    return "escalate"                             # major bumps and CVE-bearing changes go to a human
#### OUTPUT ####
60 dependency PRs, 48 risky (major bumps + CVE-bearing):
naive  "green build, merge it" : auto-merged 52   (40 false, 83.3% false-merge rate)
risk-routed sweeper            : auto-merged 12   (0 false), safe patches still applied

naive policy 自动 merged 60 个中的 52 个,其中 40 个是它绝不该碰的 risky changes

risk-routed sweeper 只自动 merged 12 个 safe patches,false merges 为零,并 escalate 其他所有内容。

这是前面的 safety router 针对 dependencies 的特化版本,也是 useful bot 和 supply-chain incident 之间的区别。

Pattern:post-merge cleanup,发现 tech debt

代码落地之后,tech debt 仍然需要被发现,这就是 post-merge cleanup 的作用。一个 cleanup loop 应该发现 self-admitted technical debt,也就是有人写下 “this is a temporary hack until the API stabilizes” 的地方。对 TODO 和 FIXME 做 keyword grep 能抓住打了标签的,但很多 debt 是用没有 marker 的普通 prose 承认的。

我在一个 labeled dataset 的 4,000 个 commits 上测量 embeddings 是否能抓住 untagged debt。

dataset 是 4,000 个 labeled commits,而 debt 是 minority class,这也是 recall 值得关注的原因。

#### OUTPUT ####
SATD dataset: 4000 commits loaded
self-admitted debt: ~7% of commits   (the rest are clean)

detector embed 每个 commit message,并 flag 那些接近 known-debt phrasing 的 messages,用与 triage 相同的 prf1 helper 评分。

# flag a commit as tech debt when its message embeds close to known-debt phrasing
score = cosine(embed(commit_msg), debt_prototype)
flagged = score >= threshold                      # recall is the objective for a review queue
m = prf1(tp, fp, fn)                              # keyword grep runs the same scoring, higher precision
#### OUTPUT ####
                precision   recall   F1     true positives
keyword grep      0.979      0.762   0.857   457  (10 false positives)
embedding model   0.834      0.855   0.844   513  (102 false positives)

我保留这个结果,正是因为它不是一个干净胜利。

embedding detector 将 recall 从 76.2 percent 提升到 85.5 percent,并发现 513 个真实 debt items,而 keyword grep 为 457 个,但它的 F1 实际下降了 1.4 points,因为它也标记了更多 false positives。

对 review queue 来说,我会接受 recall,因为漏掉一块 debt 的成本高于 human 多看一个 candidate。

正确 metric 取决于 job,而假装 F1 没有下降会传达错误教训。

Pattern:changelog drafter

我清单上的最后一个 job 是 changelog drafter。它读取 merged commits 并起草 release notes,这意味着它首先必须按 type 对每个 commit 分类:feature、fix、refactor 等。

我从 Vue core repository 的 108 个真实 commits 中去掉 conventional-commit prefixes,然后让 model 恢复 type,并用原 prefixes 作为 ground truth 评分。

classifier 给每个 commit 打 label,我们用 macro-F1 评分,这样 rare type 和 common type 权重相同。

# classify each commit, then macro-average the per-class F1 across all commit types
per_class_f1 = [prf1(tp[c], fp[c], fn[c])["f1"] for c in commit_types]
macro_f1 = sum(per_class_f1) / len(commit_types)     # rare types weighted equally with common ones
#### OUTPUT ####
9 commit types (build, chore, ci, docs, feat, fix, perf, refactor, test)
majority-class baseline : macro-F1 0.111
model classifier        : macro-F1 0.427   accuracy 0.426

model 达到 42.7 percent macro-F1,而 majority baseline 为 11.1 percent,大约好四倍,足以起草分组的、可供 human review 的 notes,让 maintainer 编辑而不是从零开始写。

Macro-F1 在这里是正确分数,因为它给 perf 这样的 rare types 与 common types 相同权重,所以 loop 不能通过总是猜 fix 来作弊。

Capstone:组合这支 orchestra

最后,我把 primitives 组合在一起,看看整体是否大于部分之和。capstone 不是新技术,而是 run-until-done loop 和 test-running checker,接成一个 pipeline。single agent 只有一次 greedy attempt。

orchestra 会 plan,运行带 execution feedback 的 refinement loop;如果仍失败,则 sample 多个 candidates,并让一个运行自己 tests 的 checker 选择 winner。

def single_agent(p):
    code, _ = agents.single_shot(llm, p, temperature=0.0, label="single")
    return agents.grade(p, code)["all_pass"]


def orchestra(p):
    # stage 1+2: run-until-done loop with execution feedback (the loop section)
    r = loops.self_refine(llm, p, max_attempts=3, use_feedback=True, label="orch")
    if r["solved"]:
        return True
    # stage 3: maker/checker fallback, sample candidates, the checker runs its own tests
    cands = agents.sample_n(llm, p, n=4, temperature=0.8, label="orch-bon")
    st = agents.gen_self_tests(llm, p, label="orch-checktests")
    bi, _ = agents.select_by_tests(p, cands, st)
    return agents.grade(p, cands[bi])["all_pass"]

在 40 个 held-out MBPP+ problems 上,per-problem column 就是证明,因为 aggregate 可能掩盖 noise。

#### OUTPUT ####
  mbppplus/132   single=0 orchestra=1
  mbppplus/446   single=0 orchestra=1
  mbppplus/560   single=0 orchestra=1
  mbppplus/293   single=0 orchestra=1
  mbppplus/773   single=0 orchestra=1
  mbppplus/244   single=0 orchestra=1
  mbppplus/734   single=0 orchestra=0
  mbppplus/260   single=0 orchestra=0

  single-agent resolve rate        0.8000
  orchestra resolve rate           0.9500
  tokens                           26844

single agent 解决率 0.80,orchestra 为 0.95,提升 15 points

这是真实的,因为正好六个 problems 从 fail 变成 pass,每个 flip 都是由实际通过 tests 的 code 赢得,而不是某个 judge 批准的。

仍有两个 problems 在所有步骤后未解决,那是 approach 本身错误的深层 failure,也是 orchestra 跨不过去的边界。

这个 lift 的成本是 orchestrated pass 的 26,844 tokens,这把前面的 budget question 具体化了。

你是在为 single-shot run、一个 refinement loop,再加上偶尔的 best-of-four sample 付费,以购买十五个点。

在这里,这个 trade 显然值得,因为额外支出买到了六个真实 fixes,而不是 noise。但我之所以能有信心这么说,唯一原因是每个 flip 都通过运行 tests 确认。

但 resolve rate 的可信度只和背后的 grader 一样高,所以 capstone 最后验证真实东西。它在真实 GitHub-issue instances 及其 gold patches 上运行官方 SWE-bench Docker harness。

#### OUTPUT ####
validating instances: ['astropy__astropy-12907', 'astropy__astropy-13033', 'astropy__astropy-13236']
Evaluation: 100%|██████████| 3/3 [03:22<00:00, 67.47s/it, done=3, error=0]
gold patches resolved by the real harness: 3/3

{ "total_instances": 3, "completed_instances": 3,
  "resolved_instances": 3, "unresolved_instances": 0, "error_instances": 0 }

官方 harness 会在每个 project 的真实环境中 apply patches 并运行真实 tests,它让 3 of 3 gold patches resolved,且 zero errors。

这确认了整篇文章所主张的 ground-truth verifier,确实在真实 infrastructure 上是 live 且 correct 的。

orchestra 在这里证明了自己的价值,但我保留的教训与 MAST finding 一致:更多 stages 不是免费的,也不总是更好。

只有当额外 verification 抓住 single agent 真实会犯的 errors 时,orchestration 才赢。在这个 slice 上它确实做到了;而当一个 strong single agent 已经足够时,它就是 overhead。

这些数字到底告诉我们什么

这里把每个组件放在一起,每个都在同一 model 和 hardware 上测量,每个都可以追溯到 results file。

nb component                    dataset (real)              baseline            with component        headline
01 run until done               MBPP+ (60)                  pass@1 0.767        0.85 with feedback     +8.3 pts (only +1.7 without feedback)
02 skills / context             sql-create-context (60)     0% executable       95% executable         0% -> 95%
03 maker / checker              MBPP+ (60, 13 wrong)        100% false-accept   31% false-accept       catches 69% of wrong code
04 memory / retrieval           project KB (14 questions)   7% closed-book      100% retrieval          7% -> 100%
05 worktrees                    real git, 6 agents          17% survives        100% survives           no silent lost updates
06 connectors / tools           data-analysis tasks (30)    10% no tool         83% with a tool         +73 pts
07 multi-loop coordination      3 loops on one repo         5 collisions, ~1M   0 collisions            collisions removed
08 budget / observability       the measured runs           n/a                 cost-quality frontier   the lift has a token price
09 safety / guardrails          57 actions (38 risky)       60.5% false auto    0% false auto           risky changes escalated
10 daily triage                 GitBugs hbase (5395)        recall 0.35         recall 0.65             ~2x the urgent reports
11 duplicate detection          GitBugs hbase (107 pairs)   recall@10 0.56      recall@10 0.65          semantic duplicates caught
12 CI sweeper                   flaky vs real (20)          fixes all 20        fixes the 10 reals      ~2M tokens saved
13 PR babysitter                MBPP+ red PRs (30, 6 red)   33% false ready     0% false ready          no broken PR marked ready
14 dependency sweeper           semver + CVE (60, 48 risky) 83% false merge     0% false merge          risky bumps escalated
15 post-merge cleanup           Maldonado SATD (4000)       recall 0.76         recall 0.86             keyword-free debt caught
16 changelog drafter            Vue commits (108)           majority 0.11       macro-F1 0.43           ~4x the baseline
17 capstone orchestra           MBPP+ (40) + SWE-bench      single 0.80         orchestra 0.95          +15 pts; 3/3 real gold resolved

我最自豪的结果,是那些没有走最容易路线的结果,而它们也是整件事中最有用的部分。

Run until done 依赖的是 feedback,而不是 retry,这就是 8.3 与 1.7 的差距。Best-of-N 对这个 model 没有任何增益,所以我们测量 verifier discrimination,而不是伪造一个胜利。

Closed book 是 7 percent,而不是 zero,因为有少数 facts 是可以猜的。tech-debt detector 中 recall 上升而 F1 下降,我们保留了这个结果。

而且 more stages are not always better,这就是 capstone 保持 MAST framing 而不夸大结论的原因。

所有这些都说明:把 loop 连接到真实、可验证的 signal,决定了组件是否真正有帮助。

下一步往哪里走

如果只带走一件事,那就是贯穿每一节的规则。

一个 loop 的质量,只取决于它所连接的可验证信号的质量。 一个连接到 model 对自己工作的 opinion 的 loop 几乎不动。

一个连接到真实 test、真实 schema、retrieved fact 或真实 evaluation harness 的 loop,则每次都会可测量地移动。

我会按照测量它的方式来构建它:一次一个组件。

先启动 engine 并证明 scorer,就像 foundations section 那样,因为后面每个数字都依赖它。

然后添加一个 primitive,测量它带来什么收益,再添加下一个,而不是一开始就组装整个 orchestra 并祈祷。

loop-engineering-measured repository 包含你刚才读到的每个 notebook、每张 chart 和每个数字,所以你可以复现它们,然后改动一件事,观察它如何变化。


【声明】内容源于网络
0
0
AI大模型观察站
专注于人工智能大模型的最新进展,涵盖Transformer架构、LLM训练优化、推理加速、多模态应用等核心技术领域。通过深度解析论文、开源项目和行业动态,揭示大模型技术的演进趋势,助力开发者、研究者和AI爱好者把握前沿创新。
内容 389
粉丝 0
AI大模型观察站 专注于人工智能大模型的最新进展,涵盖Transformer架构、LLM训练优化、推理加速、多模态应用等核心技术领域。通过深度解析论文、开源项目和行业动态,揭示大模型技术的演进趋势,助力开发者、研究者和AI爱好者把握前沿创新。
总阅读7.9k
粉丝0
内容389