AI Agent 跑不久,不是模型不够强,而是系统没设计对

人工智能知识普及,AI,大模型,Agent,prompt 但是,如果你问,从政治,经济,文化,制度等方面,来分析一下古代大一统封建王朝三百年的宿命论;虽然现在基于大模型可以实现文本,图片和视频的生成,但AI怎么实现在科学研究,医药,法律,制造业等其它领域的应用,而这就是agent需要解决的问题。至于agent它的手和脚是怎么实现的,然后又是怎么和大模型拼接到一块成为一个人的;我们知道大模型是通过大量的数据进行训练出来的一种具有“一定智能”的人工智能,之所以说具有一定智能是因为大模型目前为止并没有自己的意识,也就是说它没有像人类一样具有自己独立的思想。 阅读详情

大多数 AI agent 都只是一个单一循环:进程一重启、context window 填满,或者某个 API 调用失败,它就死掉了。用于快速聊天时这没问题,但面对需要运行数天或数周的长周期任务时,它会彻底崩溃。

在这篇博客中,我们会构建一个能在整个时间跨度内持续运行的 agent。它会规划自己的工作,构建并测试真实软件,并从失败中学习,以便在下一次运行中改进自己。为此,模型本身不需要变得更聪明。承担重活的是模型周围的系统。

连续运行一周的 agentic system(由 Fareed Khan 创建)

在开始构建之前,先看一下整体形态。人类可以通过持久信号进行审批和引导。一个 durable control plane 负责调度工作。一个 agent team 执行工作,其中有一位 lead engineer 和若干 helper。真实状态保存在 git 加分层 memory 中,而一个 deterministic verifier 是唯一被允许判定某项工作完成的东西。

  1. 我想看看我们能在相反方向上推进多远。

  2. 如果我们把长周期自主性当作工程问题,而不是模型能力,会怎样?

  3. 模型以短 burst 的方式思考,而系统通过把三件无聊的事情做得非常好来运行数周。它把真实状态保存在模型之外,记录每一步 journal,并用真实测试验证进展。

在整篇博客中,我会跟随一个具体任务,以便让这些想法落地。这个任务是从零开始构建一个小型 Python language server(一个 LSP server),系统大约需要一周时间完成。下面的 trace 是该任务的一次代表性运行,到文章末尾,你会理解其中每一行。

2026-06-02T08:44:51.880Z INFO  mission.complete items=14/14 cycles=615 head=9d8c7b6 status=DONE
2026-06-02T08:44:51.900Z INFO  governor.final cum_usd=178.60 ceiling=400.00 crashes_survived=1
                               loop_trips=1 reviewer_blocking=2 scope_cuts=1 skills_learned=2

这个单一任务经历了一次主机重启,触发过一次 loop detector,被独立 reviewer 阻塞过两次,在过度规划后自行缩减 scope,并在过程中学会了两个可复用 skill。所有这些 token 成本是 178 美元 60 美分,而预算上限是 400 美元。让我们逐步构建产生这行日志的系统。

代码概览

所有代码都位于名为 lra 的 Python package 中(Long Running Agents 的缩写)。你可以在 GitHub 上找到完整项目:

https://github.com/FareedKhan-dev/long-running-agent

代码库很大,所以这里不直接展示完整目录树,而是给出重要部分的地图。每个文件夹负责一项职责,我们会大致按这个顺序讲解它们。

src/lra/
├── durable/        # the Temporal control plane: workflows, activities, replay, claim-check
├── state/          # the git saved-state files: the real state on disk
├── contracts/      # the typed interfaces (state, model, tools, verify, memory, hitl)
├── agent/          # the inner loop: think -> act -> verify -> checkpoint, plus compaction
├── agents/         # the organization: planner, lead, researchers, reviewer, evolver, judge
├── coordination/   # ownership map, decision log, blackboard, tickets
├── model/          # pluggable LLM backends: stub, ollama, openai_compat, claude
├── execution/      # the tool dispatcher and the sandbox (local, docker, e2b)
├── safety/         # default-deny egress + the rule of two
├── verify/         # the deterministic verifier, mutation testing, trust bootstrap
├── governor/       # the budget governor + loop detector
├── hitl/           # human-in-the-loop gates
├── memory/         # tiered memory, embeddings, hybrid retrieval, skills
└── evals/          # the offline eval harness for self-improvement

lra package 如何按职责组织(由 Fareed Khan 创建)

模型层是可插拔的,所以你可以用零成本运行整个系统。本地 Ollama 模型或免费层云模型不产生费用,测试中使用 stub 模型。只有当你把它指向 Claude 这样的付费 API 时,才需要为 token 付费。下面是 quickstart:

# install (this provisions Python 3.12 for you)
uv sync
uv run lra version
uv run lra config        # show the resolved config, with secrets redacted

# run a mission locally, no servers needed (uses the stub model at $0)
uv run lra mission --task "Create a hello.py that prints hello and a test for it" /
                   --workdir .lra/workspaces/demo

这个 lra mission 命令会把任务规划成 checklist,然后逐项工作,直到每一项都通过验证。在命令和日志中,你会看到它被称为 mission,例如 lra mission,这只是系统对“一次针对一个任务的运行”的叫法。它所做的一切都是真实的 git history,因此之后你可以用 git -C .lra/workspaces/demo log --oneline 检查工作内容。现在,让我们理解为什么这个系统要这样构建。

目录

  • 核心思想:假设一定会被中断

∘ Context Window 是有损缓存

∘ 真实状态位于 Window 之外

  • Saved State

∘ Checklist

∘ 决策与事件

∘ 读取当前进展

∘ 提交 Checkpoint

  • Agent Cycle

∘ Think, Act, Verify, Checkpoint

∘ Tool Loop

∘ Context Compaction

  • 模型层

∘ 一个接口,多种 Backend

∘ 真实 Token,真实成本

∘ Failover

  • Tools 与 Sandbox

∘ Allow-List Dispatcher

∘ Sandbox

  • Deterministic Verification

∘ 由 Exit Code 决定

∘ 信任 Gate

∘ Flaky Quarantine

  • Durable Backbone

∘ Deterministic Scheduler

∘ Journaled Activities 与 Replay

∘ Durable Sleep 与 Continue-As-New

∘ Claim-Check Codec

∘ Sagas 与 Compensation

  • 不均衡的团队

∘ 为什么 Multi-Agent 是一种成本

∘ 角色与模型层级

∘ Planner

∘ Single-Writer-Per-File Ownership

∘ Researcher Fan-Out

∘ Fresh-Context Reviewer

∘ Decision Log

  • 保持安全并控制预算

∘ Budget Governor

∘ Loop Detector∘ Default-Deny Egress

∘ Rule of Two

∘ Human-in-the-Loop Gates

  • 能跨越数周的 Memory

∘ 三层 Memory

∘ Embeddings

∘ Hybrid Retrieval

∘ Consolidation∘ Skill Library

  • 让经验变成永久改进

∘ 提出更好的 Prompt

∘ Eval Harness 与 Judge

∘ Promotion Gate

  • 一个任务的一周生命周期

∘ Day Zero:研究与计划

∘ 困难部分

∘ Crash 与 Replay

∘ 摆脱卡住状态

∘ Fan-Out 与最终 Gate

  • 哪些已被证明,哪些仍是 Frontier
  • 下一步

核心思想:假设一定会被中断

在写任何代码之前,我们需要理解支撑一切的一个理念。如果一个 agent 必须运行一周,那么在这一周里它一定会被中断。机器会重启,API 会 rate limit,context window 会溢出,worker process 会被部署杀掉。中断在这里不是边缘情况,而是常态。所以我们从一开始就为它设计,而遵循的规则很简单:假设一定会被中断。

Context Window 是有损缓存

最常见的错误是把模型的 context window 当作 agent 的 memory。它不是。Context window 是一个小型、有损缓存。它最多只能容纳几十万 token,填满时会被 summarize 或 truncate,进程重启时会完全消失。如果我们的 agent 对“我在这个任务中进行到哪里了”的理解只存在于 context window,那么第一次中断就会把它抹掉。

Context window 是一个会在中断时消失的小型有损缓存(由 Fareed Khan 创建)

因此,在数千轮之后,context window 会被填满,而 crash 或 reboot 会把它丢掉。发生这种情况时,天真的 agent 会完全失去对当前状态的感知。它不知道自己已经写了哪些文件,哪些测试已经通过,或者已经做过哪些设计决策。这正是大多数长时间 agent run 崩溃的原因。我们要通过把真实状态移动到不会消失的地方来解决这个问题。

真实状态位于 Window 之外

修复方式是把任务的真实状态保存在模型之外,保存在一个能经受任何情况的地方。我们使用一个 git repository,再加上其中几个结构化文件。模型在每个 cycle 开始时读取这个状态来赶上进度,完成一小段工作,然后把结果 commit 回去。Context window 变得可丢弃,因为状态从来不在里面。

真实状态位于 git 中,每个 cycle 重新读取并提交回去(由 Fareed Khan 创建)

这张图概括了整个思想。真实状态是一个 git repo 加上一小组 saved state files。模型每个 cycle 都重新读取它,所以一个全新的、context window 为空的模型可以在几秒内重建完整认知。然后它把结果 commit 回同一个状态。Cycle 之间的 crash 不会丢失任何东西,因为唯一持久的状态就是我们已经 commit 的部分。现在,让我们构建这个 saved state。

Saved State

Saved state 是一组保存所有重要信息的文件。它位于我们正在处理的 repository 内部的 .lra/ 文件夹,并与实际代码变更一起提交到 git。这样,一个 commit 同时捕获了工作本身和工作进度。我们先看数据,因为数据就是设计。

Checklist

Saved state 的中心是 checklist。每个 item 是一个小工作单元,而且 item 只能处于四种状态之一:todo、in_progress、blocked 或 done。我们稍后会强制执行的重要规则是:只有真实测试证明后,item 才能翻转为 done。下面是用 Pydantic 写的数据模型。

# src/lra/contracts/state.py

ItemStatus = Literal["todo", "in_progress", "blocked", "done"]

class ChecklistItem(BaseModel):
    """One mission work-unit; flips to `done` only after deterministic verification."""

    id: str
    description: str
    status: ItemStatus = "todo"
    # IDs of the checks (tests/lint) that actually proved this item done.
    verified_by: list[str] = Field(default_factory=list)
    depends_on: list[str] = Field(default_factory=list)  # other item ids
    attempts: int = 0                                     # how many tries so far
    notes: str = ""
    schema_version: int = 1                               # for safe migrations later

    @property
    def is_open(self) -> bool:
        return self.status in ("todo", "in_progress", "blocked")

每个 item 都带有自己的 dependency list 和 attempt counter。verified_by 字段是一条 audit trail。它记录证明该 item 完成的 check 名称,因此之后我们可以用具体答案回答“我们怎么知道它能工作?”,而不是靠猜。Checklist 本身只是这些 item 的有序列表,加上一个非常重要的方法。

class Checklist(BaseModel):
    items: list[ChecklistItem] = Field(default_factory=list)
    schema_version: int = 1

    def next_actionable(self) -> ChecklistItem | None:
        """Return the first open item whose dependencies are all done, else None."""
        done = {i.id for i in self.items if i.status == "done"}
        for item in self.items:
            if item.is_open and all(dep in done for dep in item.depends_on):
                return item
        return None

    @property
    def all_done(self) -> bool:
        return bool(self.items) and all(i.status == "done" for i in self.items)

next_actionable 方法让 agent 总是知道下一步该做什么。它遍历列表,返回第一个 dependencies 都已完成的 open item。这使得一个没有任何 memory 的 fresh agent 也能准确接上上一个 agent 停下的位置。它不需要记住任何东西,只需要询问 checklist。

Checklist 选择 dependencies 已完成的下一个 open item(由 Fareed Khan 创建)

决策与事件

Checklist 告诉你还剩什么,但不会告诉你早先为什么做出某些选择,也不会给你逐步发生的历史。为此,我们在 checklist 旁边保存两个 append-only log。一个用于 decisions,一个用于 events。

class DecisionRecord(BaseModel):
    """A durable, never-compacted record of an implicit design decision (anti-drift)."""

    decision: str               # e.g. "use a synchronous read loop, not asyncio"
    rationale: str
    alternatives_rejected: str = ""
    affected: list[str] = Field(default_factory=list)   # item ids this touches
    cycle_id: str = ""

class EventRecord(BaseModel):
    """One entry in the append-only episodic event log."""

    kind: str                   # "cycle", "tool_call", "verification", ...
    cycle_id: str = ""
    payload: dict[str, object] = Field(default_factory=dict)
    # Pointer to a large blob in the object store, to keep the log compact.
    payload_ref: str | None = None

Decision log 比看起来更重要。当 context window 被 compact 时,最先被丢掉的往往是那些小的隐式决策,比如“我们选择直接读取 bytes,而不是使用 input()”。而这些恰恰是后来导致 drift 的决策:未来某个 cycle 悄悄违背了过去的选择。通过把它们写进一个永不 compact 的 append-only 文件,我们总能用旧决策检查新工作。

Decisions 和 events 是永远不会被 compact 掉的 append-only logs(由 Fareed Khan 创建)

读取当前进展

现在是关键操作。在每一个 cycle 开始时,agent 都会从这些文件加 git log 中重建完整认知。这就是“假设一定会被中断”这条规则变成方法后的样子。下面是来自 GitMissionAnchor class 的 read path。

# src/lra/state/mission_anchor.py

def _read_sync(self) -> SituationSnapshot:
    checklist = self._read_checklist()
    progress = ""
    progress_path = self._path(PROGRESS_FILE)
    if progress_path.exists():
        progress = progress_path.read_text(encoding="utf-8")

    # The active item is whatever is in_progress, else the next actionable one.
    active = next((i for i in checklist.items if i.status == "in_progress"), None)
    if active is None:
        active = checklist.next_actionable()

    return SituationSnapshot(
        head_sha=git_ops.head_sha(self.workdir),
        recent_commits=git_ops.log_oneline(self.workdir, 10),
        progress_summary=progress,
        open_items=[i for i in checklist.items if i.is_open],
        last_decisions=self._read_recent_decisions(5),
        active_item=active,
        is_complete=checklist.all_done,
    )

请慢慢读这个方法,因为它是整个系统的核心。它打开 checklist 文件,读取 progress narrative,找到 active item,拉取最近十个 git commits 和最近五个 decisions,并把它们打包成一个 SituationSnapshot。这个过程以毫秒计,而且对一个从未见过该任务的全新进程也有效。这就是让 crash 变成非事件的原因。下一个 worker 只需读取 snapshot 并继续。

当前状态可以在毫秒内从 git 和 state files 中重建(由 Fareed Khan 创建)

提交 Checkpoint

另一半是写入。当一个 cycle 结束时,我们写入更新后的文件,并用单个原子 git commit 提交它们。如果 commit 成功,真实状态就前进了。如果在 commit 之前发生任何 crash,真实状态就没有前进,下一个 cycle 会重试同一个 item。不存在半完成状态。

def _commit_sync(self, checkpoint: Checkpoint) -> str:
    self.anchor.mkdir(parents=True, exist_ok=True)
    self._write_checklist(checkpoint.checklist)              # checklist.json
    if checkpoint.progress_summary:
        self._path(PROGRESS_FILE).write_text(checkpoint.progress_summary, encoding="utf-8")
    if checkpoint.decisions:                                 # append, never overwrite
        with self._path(DECISIONS_FILE).open("a", encoding="utf-8") as fh:
            for decision in checkpoint.decisions:
                fh.write(decision.model_dump_json() + "/n")
    if checkpoint.events:                                    # append, never overwrite
        with self._path(EVENTS_FILE).open("a", encoding="utf-8") as fh:
            for event in checkpoint.events:
                fh.write(event.model_dump_json() + "/n")
    message = checkpoint.commit_message or f"lra: checkpoint {checkpoint.cycle_id}"
    return git_ops.commit_all(self.workdir, message)         # git add -A && git commit

注意,decisions 和 events 是 append,而不是 overwrite,而 checklist 会完整重写。整个过程以一个 git commit 结束。由于 commit message 是 deterministic 且可读的,git history 本身就变成了人类可读的 progress report。下面是一个三项小任务完成后的 history 示例。

#### OUTPUT ####
$ git -C .lra/workspaces/demo log --oneline
9d8c7b6 lra: complete 03 (task 3)
b3c4d5e lra: complete 02 (task 2)
a1f2e3d lra: complete 01 (task 1)
0c1d2e3 lra: initialize mission anchor

每个完成的 item 一个 commit,再加上创建 saved state 的初始 commit。你无需任何特殊工具,只读 git log 就能理解整个任务。这就是把真实状态放入 git 的好处。现在我们有了存储进度的地方。接下来需要真正推动进度的循环。

Checkpoint 写入 state files,并用一个原子 commit 提交它们(由 Fareed Khan 创建)

Agent Cycle

我们已经有了保存真实状态的地方。现在需要一个循环来读取它、做一点工作、再写回去。我把一个这样的工作单元称为 cycle。Cycle 被刻意设计得很小。它处理一个 checklist item,让模型运行几轮,验证结果,然后 commit。保持工作单元小,是系统具有弹性的原因:因为 crash 最多只会让我们丢失一个未完成的 cycle。

Think, Act, Verify, Checkpoint

每个 cycle 都遵循同样四步。它从 git 读取当前进展,通过模型和 tools 采取行动,用真实 checks 验证结果,并把结果 checkpoint 回 git。然后重复。

一个 cycle:读取状态,用 tools 行动,验证,然后 checkpoint(由 Fareed Khan 创建)

下面是来自 AgentLoop class 的 cycle 开头。它做的第一件事是读取 snapshot,如果任务已经完成,就立即返回。这个 early return 让整个 loop 具备 idempotent 特性。

# src/lra/agent/loop.py

async def run_cycle(self, *, ctx, mission_id, cycle_id, anchor_text, checks=None):
    checks = checks or []
    snapshot = await self._anchor.read_situational_awareness()
    # Idempotent guard: if there is nothing left to do, this cycle is a no-op.
    if snapshot.is_complete or snapshot.active_item is None:
        return CycleOutcome(
            item_id=None, advanced=False, verified=False,
            is_complete=True, head_sha=snapshot.head_sha, tool_calls=0, turns=0,
        )

    item = snapshot.active_item  # the one thing we will work on this cycle
    messages = build_messages(
        anchor_text=anchor_text, snapshot=snapshot, item=item,
        specs=self._dispatcher.specs(),   # the tools this agent is allowed to use
    )

Cycle 只选择一个 item,即 snapshot 中的 active item,并构建一个 prompt,其中包含目前的进度、open items,以及它被允许调用的 tools。注意,它从不相信自己的 memory 来决定要做什么。它总是询问刚刚读取的 snapshot。接下来进入真正思考和行动的部分。

Tool Loop

在一个 cycle 内部,模型和 tools 轮流执行。模型要么产生一个 tool call,要么发出“done”信号。如果是 tool call,我们运行 tool,把结果作为 observation 反馈回去,然后再次询问模型。我们最多重复固定轮数,因此困惑的模型永远无法在单个 cycle 内无限循环。

模型和 tools 轮流执行,直到模型发出 done 信号或达到 turn cap(由 Fareed Khan 创建)

tool_calls = 0
for turns in range(1, self._max_turns + 1):
    result = await self._model.complete(messages)     # one real model turn
    if self._ledger is not None:                      # record real token cost
        self._ledger.record(
            cycle_id=cycle_id, usage=result.usage,
            usd=self._model.estimate_cost_usd(result.usage),
        )

    action = parse_action(result.text, result.tool_calls)
    if action.done or action.tool is None:            # the model says it is finished
        messages.append(ModelMessage(role="assistant", content=result.text))
        break

    # Run the requested tool through the safety gate (more on that gate later).
    call = ToolCall(id=f"{cycle_id}-{turns}", name=action.tool, arguments=action.arguments)
    tool_result = await self._dispatcher.dispatch(call, ctx)
    tool_calls += 1
    observation = (tool_result.content or tool_result.error or "")[:4000]
    messages.append(ModelMessage(role="assistant", content=result.text))
    messages.append(ModelMessage(role="user", content=f"OBSERVATION ({action.tool}): {observation}"))

每个模型 turn 都会把真实 token usage 记录到 cost ledger,稍后我们会用它来执行预算。我们反馈回去的 observation 被限制在四千字符,因此巨大的 tool output 不会撑爆 context。当模型最终发出完成信号时,我们跳出循环并进入 verification。下面是真实 LSP 任务中的一个 cycle,lead engineer 写入 transport 文件并运行测试。

#### OUTPUT ####
07:30:04 INFO  lead.turn cycle=c1 model=claude-opus-4-8 tok_in=8112 tok_out=905 cache_read=5900 cost=0.0712
07:30:16 INFO  tool.exec cycle=c1 name=write_file path=pylsp_mini/transport.py bytes=812
08:01:59 INFO  tool.exec cycle=c1 name=run_command cmd="uv run pytest tests/test_initialize.py -q" -> exit=1
    tests/_fakeclient.py:55: in _read_response
        header = self._read_until(b"/r/n/r/n")
    E   Failed: Timeout >10.0s
08:02:10 WARN  verify.fail cycle=c1 attempt=1 reason=timeout tests=0/1

一个模型 turn,一个文件写入,一个测试运行,然后它因 timeout 失败。这是一个完全正常的 cycle。工作没有通过,所以该 item 不会被标记为 done。相反,它会被记录为一次失败尝试,下一个 cycle 会继续尝试。系统正在完全按设计运行,我们稍后会继续追踪这个 bug 的结局。

Context Compaction

单个 cycle 可以运行很多 turn,message list 会随着每次 tool call 和 observation 增长。如果某个 cycle 探索得很多,这个 list 可能长到给 window 带来压力。所以我们会 compact 它。我们完整保留 system prompt 和最近几轮,把更早的所有内容总结成一条 compact note。

Compaction 保留 system prompt 和最近 turns,并总结较早内容(由 Fareed Khan 创建)

# src/lra/agent/compaction.py

async def compact_messages(*, model, messages, keep_last=4):
    """Return system + a summary-of-older-turns + the last `keep_last` turns."""
    system = [m for m in messages if m.role == "system"][:1]    # never compact this
    body = [m for m in messages if m.role != "system"]
    if len(body) <= keep_last:
        return messages                                         # nothing to do yet

    to_summarize = body[:-keep_last]                            # older turns
    recent = body[-keep_last:]                                  # keep these verbatim
    transcript = "/n".join(f"{m.role}: {m.content}" for m in to_summarize)[:12000]
    result = await model.complete([
        ModelMessage(role="system", content=_SUMMARY_INSTRUCTIONS),
        ModelMessage(role="user", content=transcript),
    ])
    summary = ModelMessage(role="user", content=f"[compacted summary]/n{result.text[:4000]}")
    return [*system, summary, *recent]

Summary instruction 很具体。它要求模型保留任务、关键决策及其原因、发现内容包括失败方法,以及下一步。System prompt 永远不会被 compact,因为它至关重要。这让长探索可以保持在 window 内,同时不丢掉重要事实。值得注意的是,这种 in-context compaction 是 git 中 durable state 的一个更小、更快的亲戚。Git saved state 跨 cycle 和 crash 存活,而 compaction 只是让一个长 cycle 保持整洁。

模型层

Cycle 调用 self._model.complete(...),但它从不关心具体是哪一个模型。这种间接层是有意设计的。整个系统只与一个接口对话,我们可以通过一次 config 修改替换其背后的实际模型。这让同一套代码可以在测试中用本地模型零成本运行,也可以在生产中用 Claude 运行。

一个接口,多种 Backend

接口是一个小型 Protocol。模型接收一组 messages,并返回包含真实 token usage 的真实结果。仅此而已。

一个 ModelProvider 接口,可替换 stub、ollama、openai 和 claude backends(由 Fareed Khan 创建)

# src/lra/contracts/model.py

@runtime_checkable
class ModelProvider(Protocol):
    """A pluggable LLM backend. Implementations live in src/lra/model/."""

    name: str

    async def complete(self, messages, *, tools=None, max_tokens=None) -> "TurnResult":
        """Run one real model turn and return its real output + usage."""
        ...

    def estimate_cost_usd(self, usage: "Usage") -> float:
        """Compute real USD cost from real token counts (0.0 for local/free backends)."""
        ...

class Usage(BaseModel):
    """Real token accounting as reported by the provider."""

    input_tokens: int = 0
    output_tokens: int = 0
    cache_read_input_tokens: int = 0
    cache_creation_input_tokens: int = 0
    model: str = ""

这个接口有四种实现。stub 模型是 deterministic 且免费的,用于测试,这样整个系统无需网络或 key 就可以被 exercise。ollama 和 openai_compat backend 与本地或免费层云模型通信。claude backend 与 Anthropic 的 Messages API 通信。它们都返回同样的带真实 Usage 的 TurnResult,所以系统其他部分永远无需知道差异。Claude backend 甚至会单独报告 prompt cache tokens,这对长时间运行的成本非常重要。

# src/lra/model/claude.py  (the price table, dollars per million tokens)

_PRICES: dict[str, tuple[float, float]] = {
    "claude-opus-4-8": (15.0, 75.0),     # (input, output)
    "claude-sonnet-4-6": (3.0, 15.0),
    "claude-haiku-4-5-20251001": (1.0, 5.0),
}

真实 Token,真实成本

每个模型 turn 都返回真实 token count,我们把它转换成真实美元金额并追加到 ledger。关键点是这个数字是真实的。我们从 provider 自己的 response 中读取它,从不估算或伪造成本。Ledger 很小。

每个 turn 都把真实 token usage 记录到 governor 读取的 cost ledger 中(由 Fareed Khan 创建)

# src/lra/governor/cost.py

class CostLedger(BaseModel):
    entries: list[CostEntry] = Field(default_factory=list)

    def record(self, *, cycle_id: str, usage: Usage, usd: float) -> CostEntry:
        entry = CostEntry(
            cycle_id=cycle_id, model=usage.model,
            input_tokens=usage.input_tokens, output_tokens=usage.output_tokens, usd=usd,
        )
        self.entries.append(entry)
        return entry

    @property
    def total_usd(self) -> float:
        return sum(e.usd for e in self.entries)

    def mean_usd_per_cycle(self) -> float:
        cycles = {e.cycle_id for e in self.entries}
        return self.total_usd / len(cycles) if cycles else 0.0

Ledger 维护 running total 和 mean cost per cycle。这个 mean 让 budget governor 可以在下一步运行前预测成本。本地和免费 backend 记录真正的零成本,同时仍然追踪 tokens,因此即使在免费运行中,成本机制也会被 exercise。下面是 trace 中的几个模型 turn,cache reads 发挥了真实作用。

#### OUTPUT ####
07:30:45 INFO  planner.turn cycle=replan-1 model=claude-opus-4-8 tok_in=7740 tok_out=1410 cache_read=6100 cost=0.0731
07:31:40 INFO  lead.turn   cycle=c201    model=claude-opus-4-8 tok_in=9340 tok_out=1190 cache_read=8100 cost=0.0841
11:24:40 INFO  governor.tick cum_usd=103.90 tests=9/14

看一下这个 lead turn 中的 cache_read=8100。大部分 input 都来自 prompt cache,这比新 input tokens 便宜得多。在持续一周、反复读取大量相同 context 的 cycles 中,cache 是一次可负担运行和一次昂贵运行之间的差别。而 governor.tick 行显示 running total 正被追踪,此处为 103 美元 90 美分,14 个 item 中有 9 个 green。

Failover

一周时间足够长,某个 provider 总会出现故障。因此我们可以用 FailoverModel 包装多个 providers。它按顺序尝试,并返回第一个成功结果。只有当所有 provider 都失败时才 raise。

Failover 按顺序尝试 providers,并返回第一个成功结果(由 Fareed Khan 创建)

# src/lra/model/failover.py

class FailoverModel(ModelProvider):
    """Try providers in order; return the first success, raise only if all fail."""

    def __init__(self, providers: list[ModelProvider]) -> None:
        if not providers:
            raise ValueError("FailoverModel needs at least one provider")
        self._providers = providers
        self.name = "failover:" + ",".join(p.name for p in providers)

这段代码很小,但正是它让某个 API 的 transient outage 不会终止一个持续一周的任务。任务只是继续在 fallback model 上运行,而且由于模型层是一个接口,系统其他部分都不需要知道发生了这件事。还有一种 operational mode:如果所有模型都 hard down,任务会进入 durable sleep 并提醒人类,而不是消耗 retries。我们很快会看到 durable sleep。

Tools 与 Sandbox

模型不能直接接触世界。它只能请求运行某个 tool,而每个这样的请求都必须通过一个单一 gate。所有安全性都在这个 gate 中,而且它位于模型下方的代码中,而不是放在一个可以被巧妙输入说服绕过的 prompt 里。

Allow-List Dispatcher

AllowListDispatcher 是从模型请求到真实 action 的唯一路径。它在运行任何东西之前检查四件事。Tool 是否已知?是否在 allow list 上?如果该 tool 会 mutate,mutation 是否被允许?如果该 tool 会访问网络,egress 是否被允许?Egress 默认拒绝。

每个 tool call 在运行前都经过 allow-list dispatcher(由 Fareed Khan 创建)

# src/lra/execution/dispatcher.py

async def dispatch(self, call: ToolCall, ctx: ToolContext) -> ToolResult:
    tool = self._tools.get(call.name)
    if tool is None:
        return ToolResult.failure(f"unknown tool: {call.name!r}")
    if not self._permitted(call.name):
        return ToolResult.failure(f"tool not allowed: {call.name!r}")
    if tool.spec.mutating and not self._allow_mutating:
        return ToolResult.failure(f"mutating tools are disabled: {call.name!r}")
    if tool.spec.egress and not self._allow_egress:
        return ToolResult.failure(f"egress is disabled (default-deny): {call.name!r}")

    missing = _missing_required(tool.spec.parameters, call.arguments)
    if missing:
        return ToolResult.failure(f"missing required args for {call.name!r}: {sorted(missing)}")

    try:
        return await tool.run(call.arguments, ctx)
    except Exception as exc:  # a tool failure must NEVER crash the agent loop
        return ToolResult.failure(f"{type(exc).__name__}: {exc}")

这里有两点我想强调。第一,gate 在模型下方的代码中强制执行。Prompt injection 无法授予自己一个 tool,因为模型甚至不会被告知 forbidden tool 的存在。第二,最后的 try/except 意味着抛出异常的 tool 不会 crash cycle。它返回一个 failure result,成为 observation,模型可以对此作出反应。Loop 在 tool 一侧是不可打破的。不同角色拥有不同 dispatcher,因此 read-only researcher 字面意义上无法获得 mutating tool。

Sandbox

运行命令的 tools 不会在 host 上运行。它们在 sandbox 内运行,而 sandbox 只是另一个拥有三种实现的接口。开发用 local subprocess sandbox,CI 用 Docker sandbox,生产用 E2B micro VM sandbox。Agent loop 与接口通信,永远不知道底层是哪一个实现。

一个 sandbox 接口,包含 local、docker 和 E2B 实现以及 snapshots(由 Fareed Khan 创建)

# src/lra/contracts/sandbox.py

@runtime_checkable
class Sandbox(Protocol):
    name: str

    async def open(self, *, workdir: str, snapshot_id: str | None = None) -> "SandboxSession":
        """Open (or restore from snapshot_id) a session rooted at workdir."""
        ...

    async def snapshot(self, session: "SandboxSession") -> "Snapshot":
        """Persist the session's state and return a restorable handle."""
        ...

这里有意思的方法是 snapshot。Sandbox session 可以被冻结,并稍后从 handle 恢复。这对持续一周的运行很重要,因为它意味着不仅 git state,连 working environment 本身也可以在 crash 或长 sleep 后暂停并恢复。我们之前在 trace 中 host reboot 时看到过一行 sandbox.restore。有了 gated 和 sandboxed tools,我们就可以允许模型行动了。但行动不等于成功。为此,我们需要决定什么才算 done 的 gate。

Deterministic Verification

这是博客中最重要的一节,所以我想放慢速度。在数千步中,小错误会复合。如果每一步有百分之九十九的可靠性,那么几百步后你几乎肯定已经坏了。阻止这种复合的唯一方法,是永远不要相信模型自己声称某个东西可用。我们要相信真实测试。

由 Exit Code 决定

DeterministicVerifier 将每个 check 作为真实 subprocess 运行,并查看它的 exit code。只有当每个 gating check 都以 zero 退出时,一个 item 才算 done。不是模型说了算,而是 exit code 说了算。

只有真实 exit codes,而不是模型意见,才能把 item 翻转为 done(由 Fareed Khan 创建)

# src/lra/verify/verifier.py

class DeterministicVerifier:
    async def verify(self, workdir: str, checks: list[Check]) -> VerificationResult:
        results: list[CheckResult] = []
        for check in checks:
            outcome = await run_proc(check.command, cwd=workdir, timeout_s=self._timeout)
            results.append(
                CheckResult(name=check.name, passed=outcome.ok, exit_code=outcome.exit_code)
            )
        # The gate: EVERY gating check must pass. Advisory checks are recorded, not gating.
        all_green = all(
            result.passed
            for check, result in zip(checks, results, strict=True)
            if check.gating
        )
        return VerificationResult(all_green=all_green, results=results)

Python project 的默认 gate 是三条真实命令,你在 trace 中已经见过它们。

def default_python_checks() -> list[Check]:
    return [
        Check(name="ruff", command=["uv", "run", "ruff", "check", "."]),
        Check(name="mypy", command=["uv", "run", "mypy"]),
        Check(name="pytest", command=["uv", "run", "pytest", "-q"]),
    ]

回到 agent loop,这个 verifier 的结果是决定 item 命运的唯一因素。如果 all_green 为 true,item 变为 done,它的 verified_by 被填入 check names。如果不是,item 变为 blocked,attempt counter 增加。下面是 trace 中我们一直跟踪的 transport bug 最终通过的时刻。

#### OUTPUT ####
09:10:21 INFO  tool.exec cycle=c201 name=run_command cmd="uv run pytest tests/test_framing.py -q" -> exit=0
    collected 7 items
    tests/test_framing.py .......                                            [100%]
    7 passed in 0.13s
09:10:21 INFO  verify.ok cycle=c201 checks=[pytest]   <- FIRST GREEN (cycle 201, ~2.5 days in)
09:11:02 INFO  git.commit head=a1f2e3d msg="lra: transport framing (isolated, byte-tested)"

Exit code zero,七个测试通过,只有这时 verifier 才说 verify.ok。这是整个任务的第一个 green,出现在 cycle 201,大约两天半之后。紧随其后的 commit 是第一段真正可信的进展。此前的一切都只是 attempts。

信任 Gate

这里有一个微妙陷阱。只有当测试真正覆盖了代码时,green 才意味着正确。一个没有测试的 repo 也是 green,但毫无价值。因此,在我们高度依赖 gate 之前,特别是在允许 parallel writers 之前,我们会衡量自己能多大程度上信任它。我们使用两个数字:coverage 和 mutation score。

Coverage 和 mutation score 决定 verification gate 可被信任到什么程度(由 Fareed Khan 创建)

# src/lra/verify/trust_bootstrap.py

class TrustBootstrap:
    def __init__(self, *, coverage_threshold: float = 0.6, timeout_s: int = 900) -> None:
        self._threshold = coverage_threshold

    async def measure(self, workdir: str, *, command=None) -> "VerifierTrust":
        cmd = command or ["uv", "run", "pytest", "--cov", "--cov-report=term-missing", "-q"]
        result = await run_proc(cmd, cwd=workdir, timeout_s=self._timeout)
        combined = f"{result.stdout}/n{result.stderr}"
        pct = parse_coverage_pct(combined)
        return VerifierTrust(coverage_pct=pct, trusted=pct >= self._threshold, raw_tail=combined[-2000:])

Coverage 告诉我们哪些行运行过。Mutation testing 更进一步。它故意在代码中引入 bug,并检查测试是否能抓住它们。高 mutation score 是 gate 真正能发现 regression 的最强证据。下面是 LSP 任务后期的 mutation run。

#### OUTPUT ####
09:36:02 INFO  run_command cmd="uv run pytest --cov -q"  -> exit=0  "coverage: 91%"
09:48:55 INFO  run_command cmd="uv run mutmut run"       -> exit=0  "killed 89/106 (score 0.84)"
09:49:00 INFO  gate.trusted coverage=0.91 mutation=0.84

百分之九十一 coverage,测试杀死了一百零六个 mutants 中的八十九个,score 为 0.84。此时系统记录 gate.trusted,这意味着 green checkmark 现在有意义了。这就是获得信任 gate 权利的过程。

Flaky Quarantine

最后一个 verification 问题是 flaky test,也就是随机通过或失败的测试。它会污染 gate。如果 flaky check 能阻塞进度,那么 green 就变得不可靠,agent 会浪费 cycles 去追逐幽灵。因此,我们会 quarantine 已知 flaky checks。它们仍然运行,用于可见性,但会被强制设为 non-gating。

Flaky checks 仍会运行以提供可见性,但会被强制 non-gating(由 Fareed Khan 创建)

# src/lra/verify/flaky_quarantine.py

class FlakyQuarantine:
    def __init__(self) -> None:
        self._flaky: set[str] = set()

    def mark_flaky(self, name: str) -> None:
        self._flaky.add(name)

    def partition(self, checks: list[Check]) -> tuple[list[Check], list[Check]]:
        """Split into (gating, quarantined); quarantined checks are forced non-gating."""
        gating, quarantined = [], []
        for check in checks:
            if check.name in self._flaky:
                quarantined.append(check.model_copy(update={"gating": False}))
            else:
                gating.append(check)
        return gating, quarantined

被 quarantine 的 check 会被复制并设置 gating=False,所以它永远无法阻塞 item,但它仍然运行,这样我们可以随时间追踪它的 flake rate。这保持了 green 的含义干净。现在我们拥有了一个完整的 inner engine。它读取事实,通过 gated tools 在 sandbox 中行动,并且只有在可信测试通过时才标记进展。下一个问题是让这个 engine 存活一周。

Durable Backbone

Inner loop 很好,但它本身仍然只是一个 Python process。如果这个 process 死掉,loop 也随之死掉。为了运行一周,我们把 loop 包装在一个 durable execution engine 中。我们使用 Temporal。Temporal 的思想是,你的 control flow 被分成两类代码。一类是 workflow code,它必须 deterministic,并会被 journal 以便 replay。另一类是 activity code,它执行所有混乱的 non deterministic 工作,其结果被记录一次,之后 replay 时从 cache 提供。这个拆分让我们免费获得 crash recovery。

Deterministic Scheduler

MissionWorkflow 是 scheduler,它被刻意设计得小而无聊。它不包含模型调用、不包含文件 IO、不包含 randomness。它所做的一切就是一次派发一个 cycle activity,并统计已经运行了多少个。

MissionWorkflow 是一个 deterministic scheduler,每个 cycle 派发一个 activity(由 Fareed Khan 创建)

# src/lra/durable/workflows.py

@workflow.defn
class MissionWorkflow:
    @workflow.run
    async def run(self, inp: MissionInput, state: MissionState | None = None) -> MissionResult:
        state = state or MissionState()
        self._cycles_done = state.cycles_done

        while True:
            if state.cycles_done >= inp.max_cycles:
                return await self._terminal(inp, state, completed=False)

            # The ONE activity per loop. All real work happens inside it.
            result = await workflow.execute_activity(
                run_agent_cycle,
                CycleInput(
                    mission_id=inp.mission_id, workdir=inp.workdir,
                    cycle_id=f"c{state.cycles_done + 1}", check_commands=inp.check_commands,
                ),
                start_to_close_timeout=timedelta(minutes=10),
                retry_policy=_CYCLE_RETRY,           # up to 10 tries, exponential backoff
            )
            state.cycles_done += 1
            self._cycles_done = state.cycles_done

            if result.is_complete:
                return MissionResult(mission_id=inp.mission_id, completed=True,
                                     cycles=state.cycles_done, head_sha=result.head_sha,
                                     items_done=result.items_done, items_total=result.items_total)

            if state.cycles_done % inp.cycles_before_can == 0:
                workflow.continue_as_new(args=[inp, state])    # bound history (see below)

这就是整个 scheduler。读一遍并注意它不包含什么。这里没有业务逻辑。Workflow 只决定何时运行下一个 cycle,以及何时停止。由于 workflow 是 deterministic,Temporal 可以随时 replay 它的 history,并得到完全相同的状态。Retry policy 意味着如果某个 cycle activity 失败,Temporal 会用 backoff 最多重试十次,然后才放弃。真正的智能全部位于那个 activity 内部。

Journaled Activities 与 Replay

Activity 是所有 non deterministic 事情发生的地方。它运行我们之前构建的真实 AgentLoop。关键属性是它是 idempotent。它总是从 git 读取 fresh state,并推进下一个 actionable item,因此运行两次也是安全的。

Crash 会触发 replay;已完成 activities 来自 cache,因此不会重复工作(由 Fareed Khan 创建)

# src/lra/durable/activities.py

async def _execute_cycle(inp: CycleInput) -> CycleResult:
    """Advance the mission by one verified item via the real agent loop; idempotent."""
    anchor = GitMissionAnchor(inp.workdir)
    snapshot = await anchor.read_situational_awareness()   # always read truth first

    if snapshot.is_complete or snapshot.active_item is None:
        # Already done: returning early is what makes a retry a safe no-op.
        checklist = await anchor.read_checklist()
        done = sum(1 for i in checklist.items if i.status == "done")
        return CycleResult(item_id=None, advanced=False, head_sha=snapshot.head_sha,
                           is_complete=True, items_done=done, items_total=len(checklist.items),
                           note="nothing actionable")

    session = await LocalSandbox().open(workdir=inp.workdir)
    loop = AgentLoop(model=build_provider(), dispatcher=AllowListDispatcher(default_local_tools()),
                     verifier=DeterministicVerifier(), anchor=anchor)
    # ... run one cycle, then return a small CycleResult that Temporal journals ...

现在看看运行中途主机重启会发生什么。这来自 LSP trace 的第三天,当一次主机 OS update 在 mid cycle 时重启了机器。

#### OUTPUT ####
13:48:12 ERROR worker.disconnect reason="connection reset (host OS update reboot)" inflight_cycle=c190
13:55:40 WARN  temporal.replay wf=mission:mission_4c7e21a9 events=631 (completed activities served from cache; 0 tokens re-spent; 0 double commits)
13:55:41 INFO  reconcile.in_flight ticket=01 branch=feat/transport -> adopt (branch present, unmerged)
13:55:42 DEBUG orchestrator.note "replay restored state EXACTLY -> a still-RED transport. durability protects WORK, not CORRECTNESS."

这是 durability guarantee 的可视化。Worker 死了,一个新的 worker 启动,Temporal replay 了六百三十一个 events。每个已经完成的 activity 都从 cache 提供,因此没有重复花费 token,也没有重复 commit。任务恢复到它所在的精确 cycle。我很喜欢 trace 中最后一行。

Durability 保护的是工作,而不是正确性。Replay 恢复的是一个仍然 failing 的 transport,因为那就是真实状态。系统不会假装一个坏掉的东西已经修好。它只是拒绝丢失进展。

Durable Sleep 与 Continue-As-New

另外两个技巧让持续一周真正可行。第一个是 durable sleep。当没有事情要做时,比如夜间,workflow 会睡在一个 durable timer 上。这不产生成本,并且能经受 reboot。第二个是 Continue-As-New。一周运行否则会堆积巨大 event history,因此每隔若干 cycles,workflow 会滚动到一个 fresh execution,只携带一个很小的 compact state。

Durable sleep 不产生成本,而 Continue-As-New 让 event history 有界(由 Fareed Khan 创建)

# inside MissionWorkflow.run, the rollover line again:
if state.cycles_done % inp.cycles_before_can == 0:
    # Start a fresh execution carrying only the compact MissionState (pointers),
    # never the history. The new run replays prior cycles from the activity cache.
    workflow.continue_as_new(args=[inp, state])

被携带过去的 MissionState 只是一个 counter:cycles_done。其他所有内容都在 git 中。因此 rollover 很便宜,新 execution 从干净的小 history 开始。下面是 trace 中一天结束时的安静状态。

#### OUTPUT ####
18:32:04 INFO  sleep.durable until=2026-05-27T07:30:00Z reason=idle-eod
18:32:04 INFO  consolidate episodes=12 facts=5 ; governor.tick cum_usd=0.094
...
07:30:00 INFO  wake active=01 reground=git+anchor dur=0.041s

任务从傍晚睡到第二天早晨,成本为零,然后醒来并在四十一毫秒内从 git reground。这个 reground 步骤就是我们之前的 read_situational_awareness。Agent 睡觉,世界继续转动,它醒来时仍然准确知道自己在哪里。当大部分 wall clock time 都在睡眠中度过时,一周就是这样的。

Claim-Check Codec

一周内,workflow 会 journal 数千步,其中一些 payload 很大,例如大型模型 response 或长 test log。如果把它们原样 journal,Temporal history 会膨胀。因此我们使用 claim-check pattern。超过阈值的任何 payload 都存入 object store,journal 中只保存一个短的 content addressed key。

大型 payload 进入 object store,journal 中只保存短 key(由 Fareed Khan 创建)

# src/lra/durable/codec.py

class ClaimCheckCodec(PayloadCodec):
    async def encode(self, payloads):
        encoded = []
        for payload in payloads:
            if len(payload.data) > self._threshold:        # default 32 KiB
                key = await self._store.put(payload.data)   # store the bytes...
                encoded.append(Payload(
                    metadata={"encoding": _CLAIMCHECK_ENCODING,
                              "lra-orig-encoding": payload.metadata.get("encoding", b"")},
                    data=key.encode("utf-8"),               # ...journal only the key
                ))
            else:
                encoded.append(payload)
        return encoded

Object store keys 是内容的 SHA256 hash,因此相同 payload 会自动只存一次。一个十 megabyte response 在 history 中会变成六十四字符 hash。你可以在 trace 的第一行看到这一点,data converter 被配置了 claim-check threshold。

#### OUTPUT ####
09:00:01 INFO  temporal.connect addr=localhost:7233 ns=default build_id=lra-7f1c
               data_converter=ClaimCheckCodec(threshold=32768)

Codec 被接入 data converter 层,因此 activity code 根本不知道它在发生。它只是传递 objects,大型对象会被透明 offload。这让 journal 即使在一周工作后仍然足够小,可以快速 replay。

Sagas 与 Compensation

最后一个 durability 组件用于触达 git repo 外部的 action,例如创建 branch、打开 pull request 或 merge。这些 action 是多步的,中途失败会留下一团糟。因此,每个 forward step 在运行前注册一个 undo action,失败时我们按相反顺序运行 undo。

每个 forward step 注册 undo;失败时 newest-first 执行(由 Fareed Khan 创建)

# src/lra/durable/saga.py

class Saga:
    def add(self, name: str, undo: Callable[[], Awaitable[None]]) -> None:
        self._compensations.append(Compensation(name=name, undo=undo))

    async def compensate(self) -> list[str]:
        """Run compensations newest-first (LIFO); record any undo failures, never stop."""
        ran = []
        for comp in reversed(self._compensations):
            try:
                await comp.undo()
                ran.append(comp.name)
            except Exception as exc:
                self.failures.append(f"{comp.name}: {type(exc).__name__}: {exc}")
        return ran

所以如果我们创建了 branch 并打开 pull request,然后 merge 失败,saga 会按这个顺序关闭 pull request 并删除 branch。有一个 caveat。真正不可逆的 action,例如 production deploy,不会被假装 rollback。相反,它会路由到 human gate,我们很快会讲到。Durable backbone 就位后,我们的单个 agent 现在可以运行数周。下一个问题是,一个 agent 是否真的是正确形态。

不均衡的团队

到目前为止,只有一个 agent 在做所有事情。显而易见的下一步是往问题上扔一群 agents。我想反驳这个想法,因为更多 agents 并不自动更好。更多 agents 是一种成本,我们只应该在它真正带来收益时才支付它。

为什么 Multi-Agent 是一种成本

当多个 agents 同时写代码时,它们会做出相互冲突的假设,踩到彼此的文件,设计会失去一致性。Parallelism 对真正独立的工作有帮助,例如读取代码库的不同部分,或者用 fresh eyes 审查已完成工作。但对于 coupled writing,它是有害的,因为每个决策都依赖上一个。因此团队是刻意不均衡的。

一个 lead 写代码;researchers 和 reviewer 只在独立工作上 fan out(由 Fareed Khan 创建)

形态如下。一个 single threaded lead engineer 拥有所有 coupled code writing,因此设计决策保持一致。Ephemeral helpers 只在两类真正可并行化的事情上 fan out。Researchers 并行阅读,reviewer 用 fresh context 检查工作。一个单独的 integrator 是唯一允许写入 main branch 的角色。这不是 swarm。这是一个小而刻意的团队,其中昂贵部分,也就是 coherent writing,保持 serial。

角色与模型层级

每个角色由一个小 spec 描述,说明它使用哪个 model tier,以及被允许做什么。成本纪律就体现在这里。我们只在 high leverage judgment 上使用昂贵的 Opus 模型,而 bulk search 使用便宜模型。

角色映射到模型层级,使成本与判断价值成正比(由 Fareed Khan 创建)

# src/lra/agents/roles.py

class RoleSpec(BaseModel):
    name: str
    tier: ModelTier             # OPUS | SONNET | HAIKU
    system_prompt: str
    allow_mutating: bool = False
    allow_egress: bool = False
    max_turns: int = 8

# the lead is the only role that writes the integration line:
"lead": RoleSpec(
    name="lead",
    tier=ModelTier.OPUS,
    system_prompt=(
        "You are the Lead Engineer and the SOLE writer to the integration line. Keep design "
        "decisions coherent. Mark an item done only when the deterministic checks are green."
    ),
    allow_mutating=True,
),

下面是 tiers 到 roles 的映射。高判断力角色使用最强模型,而便宜的并行工作使用小模型。

  • Planner、lead 和 reviewer 使用 Opus,因为它们的判断会塑造一切。
  • Integrator 和 tester 使用 Sonnet,处理中等重量级工作。
  • Researcher 使用 Haiku,因为阅读和总结很便宜,而且会运行很多次。

这使整个团队的成本与决策价值成正比,而不是与计算量成正比。一个运行五十次的 researcher 几乎不花钱,而做出 coherent design choices 的 lead 使用值得付费的模型。

Planner

任务从 planner 开始。它接收任务描述,并将其转化为一组小型、有序、可独立验证的 checklist steps。它要求模型返回 JSON array,然后防御性地 parse,因为模型并不总是返回干净的 JSON。

Planner 将任务分解成有序、可验证的 checklist(由 Fareed Khan 创建)

# src/lra/agents/planner.py

class Planner:
    async def plan(self, *, title: str, description: str, acceptance: str = "") -> Checklist:
        messages = [
            ModelMessage(role="system", content=ROLES["planner"].system_prompt),
            ModelMessage(role="user", content=(
                f"Mission: {title}/n/n{description}/n/n"
                f"Definition of done: {acceptance or '(use your judgment)'}/n/n"
                f"{_PLANNER_INSTRUCTIONS}")),
        ]
        result = await self._model.complete(messages)
        items = parse_checklist(result.text)
        if not items:
            # Never fail to produce a plan; degrade to a single item.
            items = [ChecklistItem(id="01", description=description.strip() or title.strip())]
        return Checklist(items=items)

最后的 fallback 很重要。如果模型返回垃圾,我们不会 crash。我们从 description 生成一个单 item checklist,这样任务总能启动。下面是 planner 在 LSP 任务中的工作,我希望你注意输出里有一个令人不舒服的地方。

#### OUTPUT ####
09:47:55 INFO  checklist.write items=31 sha256=4b1c... (01 transport, 02 init, 03 hover,
               04 completion, 05 definition, 06 diagnostics, 07 formatting, 08 documentSymbol, 09 rename, ...)
09:47:55 INFO  ownership.map lead=[transport,dispatch,index] implementers=[per-feature]

Planner 生成了三十一个 items。事实证明这太多了。在核心甚至还没工作之前规划完整 server,是后续多天浪费工作的种子,我们稍后会看到。Planner 很强大,但它也会 over reach,系统需要其他机制来捕捉这一点。这本身就是一个很好的教训。Plan 是一个假设,不是合同。

Single-Writer-Per-File Ownership

当我们确实允许 parallel implementers 时,会用一个简单 invariant 防止冲突。每个文件只有一个 writer。Planner 将文件分配给 writers,而某些 shared files,例如 build manifests 和 __init__.py,只能永远属于 lead。

每个文件有一个 writer;shared files 始终属于 lead(由 Fareed Khan 创建)

# src/lra/coordination/ownership.py

class FileOwnershipMap(BaseModel):
    owners: dict[str, str] = Field(default_factory=dict)   # path -> writer id

    def permits(self, *, writer: str, path: str) -> bool:
        """Whether `writer` may write `path` under the single-writer-per-file invariant."""
        norm = _normalize(path)
        if is_shared(norm):
            return writer == LEAD               # shared files are always the lead's
        owner = self.owners.get(norm)
        if owner is None:
            return writer == LEAD               # unassigned space belongs to the serial lead
        return writer == owner

规则很容易表述。如果文件是 shared,只有 lead 可以写。如果文件有 owner,只有 owner 可以写。任何未分配区域默认属于 lead。这在 git 层检查,因此 stray write 会大声失败,而不是静默破坏另一个 implementer 的 slice。一个 writer 如果发现自己需要一个不属于自己的文件,会提交 lease request,而不是直接写。这就是我们在少数允许 parallel writing 的场合保持安全的方式。

Researcher Fan-Out

阅读是非常适合并行化的工作,因为它没有副作用。当 lead 需要 context 时,orchestrator 会同时 fan out 几个 researchers,每个研究不同问题,并收集它们的 briefs。

Researchers 并行运行,并向 lead 返回 read-only briefs(由 Fareed Khan 创建)

# src/lra/agents/team.py

async def research_fanout(*, model, dispatcher, ctx, queries, role=None) -> list[SubAgentResult]:
    """Investigate `queries` in parallel; return one brief per query (failures filtered out)."""
    role = role or ROLES["researcher"]
    agents = [SubAgent(role=role, model=model, dispatcher=dispatcher) for _ in queries]
    results = await asyncio.gather(
        *(agent.run(objective=q, ctx=ctx) for agent, q in zip(agents, queries, strict=True)),
        return_exceptions=True,
    )
    return [r for r in results if isinstance(r, SubAgentResult)]   # keep the successes

Researchers 是 read only,因此它们拿到的是 mutation disabled 的 dispatcher。它们并发运行,任何失败者都会直接从结果中过滤掉。在 LSP 任务的 day zero,lead 不知道协议的 wire format,所以第一步花在 research 上,而不是猜测。

#### OUTPUT ####
09:00:08 INFO  research.fanout n=4 task_queue=lra-research rule_of_two=ok caps=2
09:00:20 INFO  research.brief id=1 "FRAME ON BYTES. Content-Length = len(utf8(body)). headers ASCII + CRLF;
               body=utf-8 JSON-RPC 2.0. POSITIONS: 'character' is UTF-16 CODE UNITS (HAZARD-1)."
09:00:36 INFO  research.brief id=2 "pygls: read header lines off sys.stdin.BUFFER until blank; read(n) EXACTLY;
               write to sys.stdout.BUFFER. NEVER print()/input(). pylsp setmode(O_BINARY) on Windows."

四个 researchers 并行运行,并返回了清晰的 briefs。Brief one 甚至标记了一个几天后才会咬人的 hazard:UTF-16 column encoding。这就是便宜并行阅读的好处。花费几美分的 Haiku tokens,lead 在真正开始工作时已经知道问题形态。Research 没写一行代码,它只是收集事实。

Fresh-Context Reviewer

第二件适合并行化的事情是 review。单个 agent 的陷阱是 author bias。写代码的 agent 是最不适合判断代码的人。因此 reviewer 是一个具有 fresh context 的独立 agent。它只看到 diff 和 acceptance criteria,从不看到 lead 的 reasoning 或早先 attempts。

Reviewer 只用 fresh eyes 查看 diff,因此能抓住作者遗漏的问题(由 Fareed Khan 创建)

# src/lra/agents/reviewer.py

class Reviewer:
    async def review(self, *, diff: str, criteria: str, ctx: ToolContext) -> ReviewResult:
        result = await self._agent.run(
            objective=("Review the diff against the acceptance criteria. Identify correctness, "
                       "security, and scope issues. Prefix any blocking issue with 'BLOCK:'."),
            ctx=ctx,
            extra_context=f"Acceptance criteria: {criteria}/n/nDIFF:/n{diff[:8000]}",
        )
        blocking = "block:" in result.brief.lower() or "blocking" in result.brief.lower()
        return ReviewResult(brief=result.brief, blocking=blocking, tool_calls=result.tool_calls)

Fresh context 是主要思想。因为 reviewer 没见过挣扎过程,所以它不会对某种 chosen approach 产生投入。当 LSP 任务在 transport 上卡住数天时,正是 reviewer 最终指出了真正问题。

#### OUTPUT ####
10:15:40 INFO  reviewer.finding sev=blocking kind=architecture
    "you are solving THREE problems in one file and they keep colliding: (1) framing,
     (2) text-vs-binary streams, (3) concurrency. SEPARATE them. Make framing a pure byte-level
     pair read_message/write_message and UNIT-TEST it in isolation. Use sys.std*.buffer only.
     A minimal server needs NO concurrency: a synchronous read->dispatch->write loop."
10:15:40 INFO  reviewer.finding sev=advisory kind=scope
    "a broken core + 31 planned items is the real problem; cut scope until the core is green."

这一次 review 解开了整个任务。Lead 一直在 thrash,因为它把三个问题纠缠在一起。Reviewer 没有对十二次失败尝试中的任何方法产生 attachment,所以立刻看出来并要求拆分它们、缩小 scope。Blocking finding 会暂停工作并强制重新思考。这是整个运行中最清晰的例子,说明为什么独立视角值得付费。

Decision Log

我们之前已经见过 decision record 作为一种数据类型。下面是它在实践中的用法。每个有意义的设计决策都会被 append 到一个永不 compact 的 log 中,这样之后的工作就可以与它对照检查。

设计决策被 append 到永不 compact 的 log,以捕捉矛盾(由 Fareed Khan 创建)

# src/lra/coordination/decision_log.py

class DecisionLog:
    """A JSONL file of DecisionRecords. Append-only; never rewritten."""

    def append(self, record: DecisionRecord) -> None:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        with self._path.open("a", encoding="utf-8") as fh:
            fh.write(record.model_dump_json() + "/n")

    def read(self) -> list[DecisionRecord]:
        if not self._path.exists():
            return []
        lines = [ln for ln in self._path.read_text(encoding="utf-8").splitlines() if ln.strip()]
        return [DecisionRecord.model_validate_json(ln) for ln in lines]

在 reviewer 介入之后,LSP 任务的 decisions 读起来就像一段清晰的故事,讲述 approach 如何变化。

#### OUTPUT ####
07:31:02 INFO  decisions.append id=3 "narrow->14; transport-first TDD; SYNC loop; binary streams (sys.*.buffer)"

这一行记录了转折点。任务从三十一个 items 缩减到十四个,承诺对 transport 先做 test driven development,并选择 synchronous loop 和 binary streams。因为这被写下来并且永不 compact,未来任何 cycle 都不能悄悄漂移回纠缠的 async approach。Decision log 是团队关于“为什么”的长期 memory,而不只是“做了什么”。

保持安全并控制预算

一个能运行一周、能写代码、运行命令并访问网络的 agent 很强大,而强大的东西需要 guardrails。本节讨论让一次长运行不会悄悄烧钱、无限旋转或做危险事情的 guardrails。这些都不在 prompt 中。它们全部位于模型无法说服绕过的代码中。

Budget Governor

第一个 guardrail 是钱。如果 projected spend 会突破 ceiling,governor 会在下一个 cycle 运行前拒绝它。它不会等到超预算才行动,而是根据 running average 预测下一步成本,并提前停止。

如果下一 cycle 会突破 ceiling,governor 会在运行前拒绝它(由 Fareed Khan 创建)

# src/lra/governor/governor.py

class BudgetGovernor:
    """Refuses the next step BEFORE it runs if it would breach the spend/iteration ceiling."""

    def authorize_next(self, ledger, *, cycles_done, projected_usd=None) -> GovernorDecision:
        spent = ledger.total_usd
        # Estimate the next step from history if no explicit projection was given.
        estimate = projected_usd if projected_usd is not None else ledger.mean_usd_per_cycle()
        projected_total = spent + estimate

        if cycles_done >= self._max_cycles:
            return self._deny("max cycles reached", projected_total, spent)
        if projected_total > self._ceiling:
            return self._deny("projected spend exceeds ceiling", projected_total, spent)
        return GovernorDecision(allow=True, reason="within budget",
                                projected_usd=projected_total, spent_usd=spent,
                                ceiling_usd=self._ceiling)

Agent loop 在每个 cycle 顶部调用 authorize_next,如果 decision 不是 allow,任务会干净地停止,而不是 overspending。下面是 governor 授权一个 cycle,并在运行后期 tick running total 的日志。

#### OUTPUT ####
07:30:01 INFO  governor.authorize cycle=c1 allow=true projected_usd=0.06 spent=0.094
...
17:48:02 INFO  governor.tick cum_usd=31.29 tests=0/1 active=01 cycles_today=1 attempts=12

第一行显示一个 cycle 被授权,因为 projected total 远低于 ceiling。第二行晚得多,显示 transport 上艰难一天后的 cumulative spend 为 31 美元 29 美分。这个任务中 governor 从未需要干预,因为它最终以 178 美元成本完成,低于 400 美元 ceiling,但它是 stuck task 不会默默烧出一千美元账单的原因。

Loop Detector

第二个 guardrail 是时间。Agent 可能卡在反复尝试同一个失败问题上。Loop detector 监控相同 state 和 action signature 是否重复出现,超过 threshold 后就 trip 并 escalate。

当相同 state 和 action 重复太多次时,loop detector 会 trip(由 Fareed Khan 创建)

# src/lra/governor/governor.py

class LoopDetector:
    """Flags oscillation: the same (state, action) signature recurring too often."""

    def __init__(self, *, threshold: int = 3) -> None:
        self._threshold = threshold
        self._counts: Counter[str] = Counter()

    def observe(self, signature: str) -> bool:
        """Record a signature; return True once it has looped past the threshold."""
        self._counts[signature] += 1
        return self._counts[signature] >= self._threshold

这正是让 LSP 任务免于永远在 transport 上打转的机制。在相同类型的 edit 不断失败后,detector 先 warn,然后 trip,并将工作 escalate 给我们之前见过的 reviewer。

#### OUTPUT ####
17:48:02 WARN  loop_detector signature="01:transport:edit" count=3 action=warn
...
10:15:09 ERROR loop_detector signature="01:transport:rewrite" count=4 action=TRIP
10:15:09 INFO  orchestrator.pause item=01 ; escalate=reviewer reason=loop-trip

Signature 01:transport:rewrite 出现了四次,detector trip,orchestrator 没有让 lead 继续失败,而是暂停 item 并请 reviewer 介入。正是这个 escalation 产生了解开一切的 architecture finding。Loop detector 没有修复 bug。它注意到 agent 无法独自修复它,并请求帮助。

Default-Deny Egress

现在是危险部分:网络。Egress policy 默认拒绝。除非 host 明确在 allow list 上,否则不可访问。而 agent 永远不持有真实 credentials。它只看到 placeholders,broker 在最后一刻,也就是 egress boundary,换入真实 secret。

Egress 默认拒绝,broker 只在边界注入真实 secrets(由 Fareed Khan 创建)

# src/lra/safety/egress.py

class EgressPolicy(BaseModel):
    """Allow-list of permitted egress hosts (default-deny: empty = nothing allowed)."""

    allow_hosts: set[str] = Field(default_factory=set)

    def permits(self, url: str) -> bool:
        host = urlparse(url).hostname
        return host is not None and host in self.allow_hosts

class CredentialBroker:
    """Maps placeholder tokens to real secrets, injected only at egress."""

    def resolve(self, value: str) -> str:
        for placeholder, secret in self._secrets.items():
            value = value.replace(placeholder, secret)   # swap at the boundary only
        return value

好处很具体。如果 agent 被它读取的恶意网页 hijack,最坏也只能泄漏一个无用的 placeholder string,因为真实 secret 从不进入它的 context。而且它只能访问我们批准的 hosts。在 trace 中,每次 fetch 都会在旁边记录 egress decision。

#### OUTPUT ####
09:00:12 INFO  tool.exec agent=researcher-1 name=web_fetch
               url=microsoft.github.io/.../specification/#headerPart -> 200 egress=allow(microsoft.github.io)

Fetch 成功,日志显示 egress=allow(microsoft.github.io),说明该 host 在 allow list 上。对任何不在列表上的 host 的请求,在离开机器之前就会被 policy 拒绝。Network access 是 agent 被狭窄授予的 privilege,而不是默认拥有的能力。

Rule of Two

这里还有一个更深层的安全思想,它是结构性的,而不是单个 check。Meta 将其命名为 rule of two。有三种危险能力:摄取 untrusted content、访问 private data,以及能够 external communicate。同时持有三者,是让 prompt injection 变得灾难性的危险组合。因此,一个 session 最多只允许持有三者中的两种。

一个 session 最多可持有 untrusted content、private data 和 external comms 中的两项(由 Fareed Khan 创建)

# src/lra/safety/rule_of_two.py

class Capability(str, Enum):
    UNTRUSTED_CONTENT = "untrusted_content"   # web pages, issue text, ...
    PRIVATE_DATA = "private_data"             # secrets, customer data
    EXTERNAL_COMMS = "external_comms"         # egress / side effects

def check_rule_of_two(capabilities: set[Capability]) -> None:
    """Raise if a session would hold the full lethal trifecta (all three at once)."""
    if len(capabilities) >= 3:
        raise RuleOfTwoViolation(
            "session would hold untrusted content + private data + external comms "
            "(the lethal trifecta); split capabilities across sessions"
        )

Orchestrator 在授予 session 能力之前检查这一点。如果某项工作需要全部三种能力,它会被拆分到不同 sessions 中。你在 research fan-out trace 中已经看到了证明,那一行写着 rule_of_two=ok caps=2。Researchers 可以读取 untrusted web content 并访问网络,这是两种能力,但它们不能访问 private data。系统在允许它们运行之前强制执行了这个限制。这是通过结构实现的安全,而不是希望模型表现良好。

Human-in-the-Loop Gates

某些 action 风险太高,永远不应自动执行,例如 merge 到 protected branch 或 deploy。对这些 action,系统会暂停并请求人类。Gate 是 durable 的,因此可以零成本等待数天,而且关键是它有 timeout 和 default action,因此人类缺席不会让任务永远挂起。

不可逆 action 会暂停在带 timeout 和 default action 的 durable gate 上(由 Fareed Khan 创建)

# src/lra/hitl/gate.py

class AutoPolicyGate:
    """Resolves gates by policy alone (no human)."""

    async def request(self, req: GateRequest) -> GateResolution:
        if req.risk is RiskTier.REVERSIBLE and self._auto_approve_reversible:
            return GateResolution(gate_id=req.gate_id, decision=GateDecision.APPROVE,
                                  resolved_by="auto-policy")
        # Irreversible and unattended: apply the request's safe default, and mark it defaulted.
        return GateResolution(gate_id=req.gate_id, decision=req.default_action,
                              resolved_by="default", defaulted=True)

这个 gate 的 durable 版本位于 workflow 本身中,使用 signal。当需要人类时,workflow 将其 status 设为 WAITING_ON_HUMAN,并停在 condition 上,直到有人 signal 一个 decision 或 timeout 触发。下面是一个代表性的 status query。

#### OUTPUT ####
$ uv run lra mission-status mission_4c7e21a9
status=WAITING_ON_HUMAN cycles=190

$ uv run lra mission-approve mission_4c7e21a9 --decision approve
sent decision 'approve' to mission mission_4c7e21a9

健康的 parked task 显示 SLEEPING,但 WAITING_ON_HUMAN 意味着一个 gate 打开了,需要有人行动。Approve command 会 signal workflow,它会立即恢复。如果 timeout 前无人回应,default action 会触发,而对不可逆 action 来说,默认是不继续。因此 unattended run 默认安全。它永远不会悄悄做危险事情,也不会不可见地永远挂起。

能跨越数周的 Memory

我们已经有了一个能运行一周并保持安全的系统。但“存活一周”和“在一周内学习”是两回事。Saved state 让 agent 完美记住当前任务。Memory 则提供更多东西:跨 cycles,最终跨 tasks 携带经验的能力。这就是系统不再只是执行,而开始改进的地方。

三层 Memory

我们把 memory 分成三层,借用了人们讨论记忆的方式。Episodic memory 是发生过什么,也就是原始 event log。Semantic memory 是关于代码和 domain 的 distilled facts。Procedural memory 是可复用 skills,也就是经过验证的真实代码,以及它适用的条件。

Memory 有三层:episodic events、semantic facts 和 procedural skills(由 Fareed Khan 创建)

# src/lra/contracts/memory.py

class MemoryRecord(BaseModel):
    id: str
    kind: str           # "episodic" | "semantic" | "procedural"
    text: str
    metadata: dict[str, str] = {}
    embedding_model: str | None = None
    embedding_version: str | None = None
    valid: bool = True  # soft-invalidation; we never hard-delete a memory

@runtime_checkable
class SemanticIndex(Protocol):
    """Stores records and retrieves them by semantic similarity."""

    async def add(self, records: list[MemoryRecord]) -> None: ...
    async def query(self, text: str, *, k: int = 5) -> list["RetrievalHit"]: ...

注意 valid flag、embedding_model 和 embedding_version 字段。valid flag 意味着我们从不直接删除 memory,只会将其标记为 invalid,稍后会回到这一点。Model 和 version 字段用于避免一个微妙的长运行 bug:比较由不同 embedding models 生成的 vectors。数周内,你可能更换 embedder,而静默混合来自两个模型的 vectors 会给你垃圾 retrieval。

Embeddings

为了按语义搜索 memory,我们把文本转换成 vectors。Embedder 当然也是一个可替换接口。有一个真实版本会调用付费 semantic API,还有一个 deterministic offline 版本,零成本,用于测试和本地运行。

Embeddings 来自可替换 embedder,包含用于 $0 运行的 offline hash 版本(由 Fareed Khan 创建)

# src/lra/memory/embeddings.py

class HashEmbedder:
    """A deterministic, offline embedder (lexical hashing). For dev/CI/offline only."""

    def __init__(self, dim: int = 256) -> None:
        self.name, self.version, self.dim = "hash", "1", dim

    async def embed(self, texts: list[str]) -> list[list[float]]:
        return [self._embed_one(text) for text in texts]

    def _embed_one(self, text: str) -> list[float]:
        vec = [0.0] * self.dim
        for token in _TOKEN.findall(text.lower()):
            bucket = int(hashlib.sha1(token.encode("utf-8")).hexdigest(), 16) % self.dim
            vec[bucket] += 1.0                       # hash each token into a bucket
        norm = math.sqrt(sum(v * v for v in vec)) or 1.0
        return [v / norm for v in vec]               # normalize to unit length

Hash embedder 不是 semantic 的,它只是 lexical hashing,但它 deterministic 且免费,因此整个 memory subsystem 可以在无网络、无 key 的情况下运行和测试。生产中你可以在同一接口背后替换成真实 Voyage embedder。重点一如既往:系统其他部分无需改变。

Hybrid Retrieval

纯 vector search 会错过精确 keyword matches,而纯 keyword search 会错过语义。因此我们两者都运行,并融合 rankings。我们将 dense semantic search 与 lexical BM25 search 结合,使用 reciprocal rank fusion,它能融合两个 ranked lists,而不需要它们的 scores 在同一尺度上。

Dense 和 lexical search 通过 reciprocal rank fusion 融合,然后 rerank(由 Fareed Khan 创建)

# src/lra/memory/hybrid.py

def reciprocal_rank_fusion(rankings: list[list[str]], *, k: int = 60) -> list[tuple[str, float]]:
    """Fuse ranked id-lists via RRF: score = sum of 1/(k + rank)."""
    fused: dict[str, float] = {}
    for ranking in rankings:
        for rank, doc_id in enumerate(ranking, start=1):
            fused[doc_id] = fused.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(fused.items(), key=lambda pair: pair[1], reverse=True)

class HybridRetriever:
    async def query(self, text: str, *, k: int = 5, candidate_k: int = 20) -> list[RetrievalHit]:
        dense = [h.record.id for h in await self._semantic.query(text, k=candidate_k)]
        lexical = [doc_id for doc_id, _ in self._bm25.query(text, k=candidate_k)]
        fused = reciprocal_rank_fusion([dense, lexical])     # blend both rankings
        # ... map ids back to records and return the top k ...

这种 fusion 很优雅。每个 list 都贡献一个“1 除以常数加 rank”的 score,因此两个方法都高排名的 item 会浮到顶部,而只被一种方法找到的 item 仍然有公平机会。这是标准的 two stage retrieval recipe,并且完全是 pure Python,因此零成本、完全 deterministic。还有一个可选的 cross encoder reranker 用于最后润色,但 noop reranker 是免费的默认值。

Consolidation

Episodic events 增长很快。我们不想永远保存一万条 raw events。因此在 durable sleep windows 期间,一个 librarian 角色会把最近 episodes consolidation 成少数 durable semantic facts。这是模型反思自己做过的事,并写下值得保留内容的过程。

Sleep 期间 librarian 将最近 episodes 提炼为 durable semantic facts(由 Fareed Khan 创建)

# src/lra/memory/consolidation.py

async def consolidate(*, model, episodes: list[str], mission_id=None) -> list[MemoryRecord]:
    """Distill `episodes` into semantic MemoryRecord facts (model-driven, safe fallback)."""
    if not episodes:
        return []
    joined = "/n".join(f"- {e}" for e in episodes[-200:])
    result = await model.complete([
        ModelMessage(role="system", content="You are the Librarian; consolidate memory."),
        ModelMessage(role="user", content=f"{_INSTRUCTIONS}/n/nEpisodes:/n{joined}"),
    ])
    facts = _parse_facts(result.text)
    if not facts:                                       # deterministic fallback
        facts = list(dict.fromkeys(episodes))[-10:]     # keep recent distinct episodes
    return [MemoryRecord(id=new_id("fact"), kind="semantic", text=f, metadata=...) for f in facts]

Forgetting 被刻意设计得保守。我们绝不让模型原地 rewrite 一个 fact,因为这可能把 hallucination 变成 remembered truth。我们只通过 soft invalidate 将 facts 标记为 invalid。下面是 LSP 任务最艰难两天后的 consolidation step。

#### OUTPUT ####
07:00:05 INFO  librarian.consolidate window=2d episodes=29 -> lessons=4
07:00:05 INFO  librarian.lesson L1 "wire-protocol framing is byte-level + stream-mode-sensitive -> isolate & unit-test FIRST"
07:00:05 INFO  librarian.lesson L2 "never fix 3 entangled problems in one file; split them"
07:00:05 INFO  librarian.lesson L3 "stream.read(n) on a pipe can short-read -> loop until n bytes"

一夜之间,二十九个 raw episodes 变成了四条清晰 lessons。读一下这些 lessons。它们正是任务在困难阶段痛苦学到的东西。第二天早上,当 lead 重新规划时,这些 lessons 已经在 context 中,这就是第二次尝试顺利得多的原因。系统睡觉、反思,并醒来时知道了更多。

Skill Library

最高层是 procedural memory,也就是一个可复用 skills 的真实 library。Skill 是代码加它适用的 preconditions。关键规则是:只有当 skill 被验证过,也就是学习时通过 deterministic test gate,才能被收入 library。

Skill 只有在通过 deterministic test gate 后才进入 library(由 Fareed Khan 创建)

# src/lra/memory/skills.py

class InMemorySkillStore:
    async def add(self, skill: Skill) -> None:
        if not skill.verified:
            raise SkillNotVerifiedError(
                f"refusing to admit unverified skill {skill.id!r} (must pass the test gate first)"
            )
        self._by_id[skill.id] = skill
        await self._index.add([MemoryRecord(
            id=skill.id, kind="procedural", text=skill.description,
            metadata={"namespace": skill.namespace, "name": skill.name},
        )])

Store 直接拒绝接纳 unverified skill。这防止 library 被看似合理的 nonsense 填满。Skill 还携带 provenance 和 expiry,因此 lesson 不会永远僵在那里。观察 LSP trace 中的 skill gate,先拒绝再接纳。

#### OUTPUT ####
18:35:10 INFO  skill.admit count=0 reason="nothing passed the gate; the system records no progress it didn't earn"
...
09:11:02 INFO  skill.confirm name=lsp-framing precond=[python,stdio] proven_by=tests/test_framing.py
11:50:02 INFO  skill.admit name=lsp-utf16-offsets proven_by=tests/test_positions_unicode.py

第一行是我在整个运行中最喜欢的一行。经过一整天失败尝试后,零个 skills 被接纳,因为没有任何东西通过 gate。系统不记录任何它没有赢得的进展。随后,一旦 framing 最终通过测试,skill lsp-framing 被确认,并以证明它的 test file 作为证据。后来,UTF-16 fix 也赢得了自己的 skill。Library 只包含被证明可工作的东西。这就是 skill 在下一个任务中安全复用的原因。

让经验变成永久改进

我们刚构建的 memory 有助于单个任务内部。但最有趣的学习发生在 tasks 之间。一个任务结束后,系统可以回顾自己的失败,并改进自身 prompts,让下一个同类任务做得更好。这是 self improvement,而且必须非常谨慎,因为如果天真地让 agent rewrite 自己的 instructions 是危险的。

提出更好的 Prompt

第一步完全离线发生,在 tasks 之间,从不发生在 live run 上。Evolver 读取已完成任务的 failure traces,并为某个角色提出改进后的 system prompt。这是一种 reflective optimization,精神上类似 GEPA。重要的是,输出只是 candidate。还没有任何东西被改变。

离线时,evolver 读取 failure traces 并提出 candidate prompt(由 Fareed Khan 创建)

# src/lra/agents/evolver.py

class PromptEvolver:
    """Proposes an improved prompt for a role from its failure traces (offline)."""

    async def propose(self, *, role, current_prompt, failure_traces) -> PromptCandidate:
        traces = "/n".join(f"- {t}" for t in failure_traces[-50:]) or "(no traces)"
        result = await self._model.complete([
            ModelMessage(role="system", content=(
                "You optimize an agent's SYSTEM PROMPT by reflecting on its failures "
                "(GEPA-style). Output ONLY the improved prompt text, no preamble.")),
            ModelMessage(role="user", content=(
                f"Role: {role}/n/nCurrent prompt:/n{current_prompt}/n/n"
                f"Failure traces:/n{traces}/n/nReturn an improved prompt.")),
        ])
        candidate = result.text.strip() or current_prompt
        return PromptCandidate(role=role, prompt=candidate,
                               rationale="reflective optimization over failure traces (eval-gated)")

Evolver 读取出了什么问题,并写下它认为会更好的内容。对于 LSP 任务,它注意到几天时间浪费在一个困难的 wire protocol 问题上,并且与 over scoping 纠缠在一起,因此提出了一些 standing rules 来避免下次重蹈覆辙。

#### OUTPUT ####
22:14:40 INFO  evolve.candidate diff="+ for stdio/wire-protocol: implement+unit-test byte-level framing
    in ISOLATION first (incl. a short-read stream + a multibyte body); use binary streams; start
    synchronous. + if an unproven core has >~10 dependent items, cut scope until the core is green."

这个 candidate 是痛苦的一周经验,被压缩成 lead engineer 的几条 standing rules。但 candidate 只是 proposal。我们还不信任它。它必须赢得自己的位置。

Eval Harness 与 Judge

为了判断 candidate prompt 是否真的更好,我们让它在 held out 的 gold cases 上运行并评分。Scoring 使用一个独立 agent 作为 judge,并配有 rubric,因为很多标准是 fuzzy 的,不是简单 pass 或 fail。

Candidate 由独立 agent-as-judge 在 gold cases 上评分(由 Fareed Khan 创建)

# src/lra/evals/harness.py

async def run_eval_suite(*, model: ModelProvider, cases: list[EvalCase]) -> EvalReport:
    """Judge each case and aggregate into a report (pass rate + mean score)."""
    judge = AgentAsJudge(model)
    results = []
    for case in cases:
        verdict = await judge.judge(candidate=case.candidate, rubric=case.rubric)
        results.append(EvalCaseResult(name=case.name, passed=verdict.passed,
                                      score=verdict.score, rationale=verdict.rationale))
    return EvalReport(results=results)

Judge 被刻意设计成独立于它所评判的 agents,并且对 unparseable output 默认 fail,因此不会悄悄通过某些东西。下面是 eval 对比旧 prompt 与 evolved prompt 的结果。

#### OUTPUT ####
22:14:55 INFO  eval.run set=wire-protocol-gold n=20 baseline=0.71 candidate=0.85
22:15:30 INFO  judge.verdict model=claude-opus-4-8 promote=true delta=+0.14 regressions=0

在二十个 gold cases 上,baseline prompt 得分 0.71,evolved prompt 得分 0.85。Judge 建议 promotion,提升 0.14 且 zero regressions。现在 candidate 背后有了证据。但我们仍然不会直接 ship 它。

Promotion Gate

最终 gate 最严格。Candidate 只有在 evals 上击败 baseline,并且通过 recorded task histories 的 replay,证明它不破坏 determinism 后,才会被 promotion;即便如此,它也会以 pull request 形式发出,需要人类批准。Live agent 的 prompt 永远不会被 hot patch。

Candidate 只有经过 evals、clean replay 和 human approval 后才会 promotion(由 Fareed Khan 创建)

# src/lra/agents/evolution.py

async def evolve_and_gate(*, model, role, current_prompt, failure_traces,
                          baseline_cases, candidate_cases, min_improvement=0.0) -> EvolutionResult:
    """Propose an improved prompt and promote it ONLY if it beats baseline by min_improvement."""
    candidate = await PromptEvolver(model).propose(
        role=role, current_prompt=current_prompt, failure_traces=failure_traces)
    baseline = await run_eval_suite(model=model, cases=baseline_cases)
    evolved = await run_eval_suite(model=model, cases=candidate_cases)
    promoted = evolved.pass_rate >= baseline.pass_rate + min_improvement
    return EvolutionResult(promoted=promoted, candidate=candidate,
                           baseline_pass_rate=baseline.pass_rate,
                           candidate_pass_rate=evolved.pass_rate,
                           reason=("candidate improved" if promoted else "rejected"))

Gate 是一条 independent checks 链:evals、judge 和 replay。下面是 trace 中最终 promotion step。

#### OUTPUT ####
22:15:31 INFO  replay.histories n=9 result=9/9 clean
22:15:31 INFO  evolve.promote target=prompts/lead.md mode=PR human_approval=required (never hot-patched a live run)

九条 recorded histories 在新 prompt 下 clean replay,只有这时变更才被 promotion,作为 pull request,并要求 human approval。那一行末尾的短语就是 self improvement 的完整安全哲学。系统永远不会 hot patch live run。它提出、证明、请求,然后改进才会落地。下一个 wire protocol 任务会跳过大部分多天浪费工作,因为经验已经被烘进 lead 的 standing instructions 中。这个 loop 值回了票价。

一个任务的一周生命周期

我们已经构建了每个组件。现在让我们看它们如何在完整一周中协同工作,任务就是我一直引用的那个:从零构建一个小型 Python language server。Language server 通过 standard input 和 output 使用特定 wire protocol 通信,而这会变成一个出乎意料棘手的小问题。本节是整个运行,从开始到结束,这样你可以看到我们构建的组件在真实压力下实际如何表现。

Day Zero:研究与计划

任务从一个命令开始。Workflow 启动,saved state 初始化,governor 读取它的 ceiling。然后,lead 没有猜测协议,而是聪明地把第一步花在 research 上。

Day zero:research fan-out 供给 plan,而 plan over-scope 到 31 items(由 Fareed Khan 创建)

#### OUTPUT ####
$ uv run lra mission-start --title pylsp-mini --workdir .lra/workspaces/pylsp-mini --task @mission.txt
09:00:01 INFO  workflow.started wf=mission:mission_4c7e21a9 task_queue=lra-mission
09:00:01 INFO  governor.config ceiling_usd=400.00 max_cycles=2000 stall_limit=5
09:00:02 DEBUG planner.thinking "I do not have the LSP wire format memorised. Guessing will burn cycles.
               Spend on READ-research first: base protocol framing, a real server's transport, exact JSON."
09:47:55 INFO  checklist.write items=31  (01 transport, 02 init, 03 hover, 04 completion, ...)

Plan 生成了三十一个 items,这太多了。在 transport 甚至还没工作之前规划整个 server,就是我们前面指出的 over scope,也是后续浪费工作的种子。但系统当时还不知道。它 commit plan 并开始工作。这很现实。优秀工程师也会 over plan。关键是系统能否从中恢复,而它可以。

困难部分

Transport 是第一个 item,而且非常棘手。Lead 写了一个 transport,运行测试,失败。再试一次,又以不同方式失败。整整一天里它尝试了十二次,每次修复上一个症状,又暴露一个新症状。没有 header 的 bare JSON,然后是 Windows newline translation 导致的两字节 mismatch,然后是 pipe 上的 short read,然后是 asyncio deadlock。

十二次 transport attempts,每次修复一个症状并暴露下一个(由 Fareed Khan 创建)

#### OUTPUT ####
09:20:38 verify.fail attempt=3 reason=byte-mismatch delta=+2  # Windows /n -> /r/n on text-mode stdout
10:11:40 verify.fail attempt=6 reason=JSONDecodeError "Unterminated string"  # pipe short-read
11:11:02 verify.fail attempt=10 reason=Timeout >12s  # asyncio executor sometimes never returns
17:48:02 WARN  loop_detector signature="01:transport:edit" count=3 action=warn

这些失败中的每一个都是真实 bug,一个人类在 Windows 上写 language server transport 时也会碰到。重要的是最后一行。Loop detector 已经开始计数。系统注意到同一个 item 正以同样方式反复失败。它尚未干预,但它在观察,而这个观察马上会变得重要。

Crash 与 Replay

第三天,transport 仍然 red,主机机器因为 OS update 重启,正好发生在 cycle 中间。这是会杀死普通 agent 的灾难。在这里,它是非事件。

Mid-cycle reboot 通过 replay 恢复,恢复精确先前状态(由 Fareed Khan 创建)

#### OUTPUT ####
13:48:12 ERROR worker.disconnect reason="connection reset (host OS update reboot)" inflight_cycle=c190
13:55:40 WARN  temporal.replay events=631 (completed activities served from cache; 0 tokens re-spent; 0 double commits)
13:55:41 INFO  reconcile.in_flight ticket=01 branch=feat/transport -> adopt (branch present, unmerged)
13:55:42 DEBUG orchestrator.note "replay restored state EXACTLY -> a still-RED transport."

Worker 死了,一个新的起来了。Temporal replay 了 journal,从 cache 提供已完成工作,任务从精确 cycle 继续。没有 token 重花,也没有 duplicate commits。注意,它恢复的是仍然 failing 的 transport,因为那就是真实状态。Durable backbone 保护工作,但不假装 bug 已修复。Crash 前任务卡住,crash 后仍然卡住,这完全正确。

摆脱卡住状态

最终打破 deadlock 的不是一次幸运 edit。而是 loop detector trip,进而 escalate 给 reviewer,reviewer 给出我们之前看到的 architecture finding。夜间,librarian 将失败 consolidation 成 lessons。第二天早上,planner 带着这些 lessons 重新规划,缩小 scope,lead 将 transport 重建为 isolated、tested unit。

Lessons 加 reviewer finding 促成 re-plan 和第一个 green(由 Fareed Khan 创建)

#### OUTPUT ####
07:00:06 INFO  reflection.write item=01 "the 12 attempts were the same 3 bugs reshuffled. stop editing
               transport.py in place; build framing as a tested unit, sync, binary."
07:31:02 INFO  state.migrate checklist v1->v2 items=14 (dropped 06-09, plus 4 sub-items)
09:10:21 INFO  verify.ok cycle=c201 checks=[pytest]   <- FIRST GREEN (cycle 201, ~2.5 days in)

读一下这个序列。Reflection 指出了真正问题:十二次尝试只是同三个 bug 的重新排列。Checklist 从三十一个 items 迁移到十四个。在 cycle 201,大约两天半后,transport 终于通过了测试。这是整个任务的第一个 green。此前的一切都是 attempts,此后的一切建立在坚实基础上。这次恢复来自系统自己的 machinery:loop detector、reviewer、librarian 和 re-planner 都在各司其职。

Fan-Out 与最终 Gate

一旦 core green,剩下的部分就很快。Features 彼此独立,因此它们 fan out 给 parallel implementers。Day zero research 标记的一个旧 hazard,UTF-16 column encoding,终于咬人并被修复。然后整个系统通过最终 gate。

Core green 后,features fan out,UTF-16 hazard 被修复,最终 gate 通过(由 Fareed Khan 创建)

#### OUTPUT ####
07:30:03 INFO  child.spawn implementer:0 ticket={feature:hover, write_set:[features/hover.py, tests/test_hover.py]}
07:30:03 INFO  child.spawn implementer:1 ticket={feature:completion, write_set:[features/completion.py, ...]}
08:58:12 INFO  child.done implementer-0 branch=feat/hover verify=ok skill_reused=lsp-framing
09:31:45 INFO  reviewer.finding sev=blocking "HAZARD-1 from day0: LSP 'character' = UTF-16 CODE UNITS;
               you used Python str indices. convert at the boundary."
08:43:02 INFO  run_command cmd="make check" -> exit=0   "ruff: clean; mypy: clean; pytest: 58 passed"
08:44:51 INFO  mission.complete items=14/14 cycles=615 head=9d8c7b6 status=DONE

这些行里有很多值得喜欢的地方。Implementers 在 disjoint files 上并行运行,其中一个复用了困难阶段学到的 lsp-framing skill,因此无需重新发现 transport。Reviewer 抓住了从 day zero 就潜伏着的 UTF-16 hazard。最终 gate make check 干净通过,五十八个 tests green。任务在六百一十五个 cycles 中完成了全部十四个 items。这就是我们开篇展示的那一行,现在其中每个词都应该有意义了。

哪些已被证明,哪些仍是 Frontier

有必要清楚说明这个系统是什么、不是什么,因为 agent demo 很容易被过度包装。下面是清晰区分。

以下部分已被证明并测试过。它们是工程,今天就能工作。

  • Durability 和 crash recovery。Mid run reboot 不会丢失工作,也不会重复花费 tokens。
  • State 位于模型之外。它保存在 git 中,因此任何 restart 都能在几秒内重建全局图景。
  • Deterministic verification。真实 tests,而不是模型意见,gate 每一段进展。
  • Safety boundaries。Tool gate、default deny egress、budget governor 和 loop detector 都在代码中强制执行。
  • Tiered memory 和 skill reuse,并且 skills 只有通过 gate 后才被接纳。

下面是 frontier,也就是我不会过度声称的部分。完全无人值守、多周自主性,即模型能可靠驱动复杂工作数天而无需任何人类介入,这还不是一个已解决问题,而且今天没有任何模型能可靠做到。我们能高可靠信任的 task horizon 仍然远低于一天。

哪些已被证明和测试,哪些仍是 frontier(由 Fareed Khan 创建)

因此,这个系统的主张是精确的。系统可以运行数周。它会睡眠、存活、恢复,并且永远不会忘记自己在做什么,因为 durability 和 verification 是系统的工程属性,而不是我们寄希望于模型拥有的能力。模型以短而已验证的 bursts 驱动工作,不可逆 action 由 human gates 把关。这就是这篇博客标题的含义。持久性是一种工程属性,而不是模型能力。我们没有让模型变得更聪明。我们构建了一个系统,让今天的模型可以完成一周的谨慎工作,而不至于崩溃。

下一步

如果你想在此基础上构建,我会建议从小处开始,并让每一层都自己赢得位置。先让 git saved state 和 single cycle loop 跑起来,把 deterministic verifier 作为 gate,并证明 mid run crash 不会丢失任何东西。这个 durable single agent backbone 是困难且有价值的核心,其他一切都是附加层,只有在可证明有帮助时才值得加入。当 author bias 开始让你付出代价时,再加入 reviewer。当阅读成为瓶颈时,再加入 researchers。当你反复运行同类任务时,再加入 memory 和 self improvement。

完整代码,包括 Temporal workflows、agent team、memory tiers 和 offline evolution loop,都在 GitHub 上:

https://github.com/FareedKhan-dev/long-running-agent

感谢你读到这里。我们从一个简单思想——假设一定会被中断——出发,把它发展成一个能在真实软件任务上工作一周、经受 crashes 和 reboots、保持预算、不越过安全边界,甚至从自身失败中学习的系统。模型以短 bursts 的方式完成思考。工程完成了存活。

这里给大家精心整理了一份全面的AI大模型学习资源,包括:AI大模型全套学习路线图(从入门到实战)、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等,资料免费分享!

👇👇扫码免费领取全部内容👇👇
在这里插入图片描述

1. 成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。

这里,我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。
在这里插入图片描述

2. 大模型经典PDF书籍

书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。(书籍含电子版PDF)

在这里插入图片描述

3. 大模型视频教程

对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。

在这里插入图片描述

4. 2026行业报告

行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

5. 大模型项目实战

学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

在这里插入图片描述

6. 大模型面试题

面试不仅是技术的较量,更需要充分的准备。

在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

在这里插入图片描述

7. 资料领取:全套内容免费抱走,学 AI 不用再找第二份

不管你是 0 基础想入门 AI 大模型,还是有基础想冲刺大厂、了解行业趋势,这份资料都能满足你!
现在只需按照提示操作,就能免费领取:

👇👇扫码免费领取全部内容👇👇
在这里插入图片描述

浅谈人工智能之基于AutoGen Studio+Ollama本地大模型构建AI Agent 随着人工智能技术的飞速发展,大型语言模型已成为推动创新应用的关键力量。然而,云服务的使用虽然便捷,但面临着数据安全、延迟及成本等挑战。为解决这些问题,越来越多的开发者选择在本地部署大模型。本文将介绍如何利用AutoGen Studio与Ollama这一强大的组合,在本地搭建高效、安全的AI Agent。通过这一方案,开发者不仅能够享受大模型的强大功能,还能确保数据的私密性和降低运行成本。 阅读详情

相关推荐

读懂AI Agent:基于大模型的人工智能代理

⼤语⾔模型的浪潮推动了AIAgent相关研究快速发展,AIAgent是当前通往AGI的主要探索路线。⼤ 模型庞⼤的训练数据集中包含了⼤量人类行为数据,为模拟人类的交互打下了坚实基础;另⼀⽅⾯, 随着模型规模不断增大,大模型涌现出了上下⽂学习能力、推理能力、思维链等类似⼈类思考方式的多种能力。将大模型作为AIAgent的核⼼⼤脑,就可以实现以往难以实现的将复杂问题拆解成可实现 的子任务、人类的自然语言交互等能力。

qq_25438419的博客 1726

企业AI Agent落地真相:不是技术不够强,而是人和流程没理顺!

文章揭示企业部署AI Agent的三大障碍:工作流集成(60%)、员工抵触(50%)和数据隐私(50%),指出最大挑战非技术而是人和流程问题。成功落地的AI Agent需从小处着手,提供"保姆式"服务,并根据行业精准定位价值。目前62%的AI Agent公司已获得企业"业务线预算",表明其正从实验工具转变为生产力。未来AI Agent发展需平衡准确率与自主性,并像情商高的真人一样主动洞察用户需求。

2401_84495872的博客 786

AI Agent在芯片设计领域的未来应用

AI Agent在芯片设计领域的未来应用

danxutj的博客 1380

【AI大模型】生成式AI的未来——CHAT还是AGENT?

AI Agent是**人工智能代理(Artificial Intelligence Agent)**的概念,它是一种能够感知环境、进行决策和执行动作的智能实体,通常基于机器学习和人工智能技术,具备自主性和自适应性,在特定任务或领域中能够自主地进行学习和改进。一个更完整的Agent,一定是与环境充分交互的,它包括两部分——一是Agent的部分,二是环境的部分。此刻的Agent就如同物理世界中的「人类」,物理世界就是人类的「外部环境」。

Skrrapper的博客 9564

Excel药品说明书数据库:从零开始搭建医药数据查询系统(附完整模板)

本文详细介绍了如何从零开始搭建Excel药品说明书数据库,提供完整的医药数据查询系统模板。涵盖数据库架构设计、数据清洗标准化、智能查询系统搭建及维护优化,帮助医药从业者高效管理药品信息,提升查询速度和准确性。

weixin_28327051的博客 235

UE4_UE5 自定义简单水材质(UE材质图表大全1)

首先是材质的颜色节点、粗糙度节点、透明度节点。(1/2/3/4键+右键空白处,可创建一/二/三/四维节点)接着是两个法线波动节点的叠加。最后是折射节点,将fresnel菲涅尔节点连接到材质结果节点的折射输入端。到这里,你基本已经实现了一个波纹流动的水材质。后续我会更新加上海面浪花的水面材质,可以到我的专栏中寻找。有问题随时私信沟通,加油❤。

shanlinhan的博客 1989

AI Agent 基于大模型的人工智能代理

大语言模型的浪潮推动了 AI Agent 相关研究快速发展,AI Agent 是当前通往 AGI 的主要探索路线。大模型庞大的训练数据集中包含了大量人类行为数据,为模拟类 人的交互打下了坚实基础;另一方面,随着模型规模不断增大,大模型涌现出了上 下文学习能力、推理能力、思维链等类似人类思考方式的多种能力。将大模型作为 AI Agent 的核心大脑,就可以实现以往难以实现的将复杂问题拆解成可实现的子任 务、类人的自然语言交互等能力。

2301_79455190的博客 1686

手把手教你用ObjectCRX进行Caxa二次开发:从零开始的环境搭建教程

本文是一份面向零基础初学者的Caxa二次开发实战指南,重点讲解如何使用ObjectCRX框架从零开始搭建开发环境。内容涵盖Visual Studio 2015版本锁定、ObjectCRX向导集成、项目创建、典型错误解决,并最终实现一个可在Caxa中运行的HelloWorld程序,帮助开发者快速入门并避开常见陷阱。

w3x4y的博客 514

全球首个AI设计Agent内测了,手慢就没资格了

最近圈子里被一个新词刷屏了:“AI设计代理”。一开始我也没在意,直到某位资深设计师朋友神秘兮兮发我一个链接,说:“别问了,这可能是你今年唯一值得抢的AI工具,赶紧去排队。我点进去一看——是。它自称“全球首个AI设计代理”,可以用一句话命令自动完成海报、LOGO、视频、甚至3D动画设计,彻底颠覆了我对“AI画图工具”的认知。。也就是说,它不是帮你“画个图”,而是直接接手一整个项目。你说出一句话,比如:“我要一个文艺风格的香氛品牌全套视觉。

xiaoganbuaiuk的博客 3261

【LTspice】002 第一个仿真电路

目录 1. 光偶元件的工作原理 2. 过零检测电路的工作原理 3. 以PC817光耦搭建一个过零检测电路 1. 光偶元件的工作原理 光耦合器(optical coupler,英文缩写为OC)亦称光电隔离器或光电耦合器,简称光耦。它是以光为媒介来传输电信号的器件。 PC817是一种把红外光发射器件和红外光接受器件以及信号处理电路等封装在同一管座内的器件。当输入电信号加到输入端发光器件LED上,LED发光,光接受器件接受光信号并转换成电信号,然后将电信号直接输出,或者将电信号放大处理成标准数字电平输

m0_59642765的博客 8024

python基础语法与数据结构

本文摘要: Python基础语法包含输入输出、函数、变量、数据类型及运算符等内容。输入函数input()获取键盘输入并以字符串存储,输出函数print()可格式化输出数据。函数通过def定义,可带参数和返回值,提高代码复用性。变量无需声明类型,可动态赋值。数据类型包括数字、字符串、布尔及复合类型,可通过type()查看。运算符包含算术、赋值等类型,其中复合赋值运算符如+=可简化运算。类型转换函数如int()、str()等可实现显式类型转换。这些基础语法构成Python编程的核心要素。

MM602388的博客 436

2025核心概念 AI Agent:什么是AI Agent?AI Agent 与大模型的区别

经过以上的分享,大家都直观感受到了AI Agent的重要意义。。DeepSeek,Qwen等强力大模型的出现为AI Agent提供了良好的模型基础,使其可以构建更大更强的智能体系统。AI Agent的发展又让AGI(通用人工智能,旨在创造一个能像人类一样思考、学习、执行多种任务的系统)的美好愿景逐渐成为现实,未来的世界一定是人工智能的世界!

老皮的博客 3805

三菱FX5U气缸控制功能块(完整ST源代码+示例程序)

如果机器包含100个气缸,我们只需要修改数组的元素数量就可以了,效率非常的高。待续....博途PLC 面向对象系列之“双通气缸功能块“(SCL代码)_plc面向对象-优快云博客文章浏览阅读667次。本文介绍了博途PLC中面向对象编程的应用,重点讲解了双通气缸的功能块设计,包括双通电磁阀功能块、双通气缸的UDT数据类型以及SCL代码实现。同时,对比了双通与单通电磁阀的差异,并讨论了将单通气缸改造为双通气缸的方法,强调了双通气缸在安全性上的优势。

RXXW_Dor的博客 1032

AI Agent失败真相:不是模型不够强,而是上下文工程没做好!大模型开发指南

本文深入探讨上下文工程(Context Engineering)的概念与实践,指出多数AI Agent失败源于上下文管理不善而非模型能力不足。文章系统阐述了上下文工程的三大维度:动态上下文管理、信息熵控制和系统级协议,并提出了"写入、选取、压缩、隔离"四大实践方法。上下文工程作为一门系统性学科,旨在为AI Agent智能组装最优上下文组合,是构建高效AI应用的关键方法论。

m0_74942241的博客 708

STM32芯片解锁读写保护:更改Option Byte [Error已解决]

解锁读保护:STLink更改Option Byte 变Level从1变为0,点击Apply即可。(程序执行读保护,其实是将Level从0变为1) 默认情况下该保护功能是不开启的,若要开启该功能,需要改写内部FLASH选项字节(Option Bytes)中的配置。 RDP读保护级别 级别0,无保护, 级别1,使能读保护, 这种保护下,若使用调试功能(使用下载器、仿真器)或者从内部SRAM自举时都不能对内部FLASH及备份SRAM作任何访问(读写、擦除都被禁止); 当芯片处于级别1的时候,可以把选项字节

HowieXue 薛永浩的博客 2万+

C#实现将DataGridView中数据导出到Excel (附完整源码)

C#实现将DataGridView中数据导出到Excel (附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1589

别再卷模型参数了!Agent系统崩盘的真正原因:不是模型不够强,而是不会“协作“!程序员必看

文章指出企业级AI Agent落地面临的核心挑战不是模型能力不足,而是协作机制缺失。随着大模型能力差距缩小,智能路由成为解决工具选择、任务流转、流程控制等问题的关键基础设施。Agent应用重心正从单体能力转向多Agent协同,其规模化可行性取决于系统的可复用性、可管理性、可监控性和可扩展性。真正的智能系统不是单个"AI员工",而是具备可靠协作能力的智能协作体系。

m0_59163425的博客 694

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

超干货!如何设计基于Agent的AI应用系统

随着我们对人工智能的研究,我们越来越开始理解人类大脑的工作原理。本文通过对复旦论文的解读,提出了一种可以实现自执行的任务处理系统架构。一个agent本身的实现可以很复杂,但是在概念上,它只是一个“input -> agent -> output”的处理单元,是非常简单的。随着我们在技术上的深入,我们会越来越接近AI编程。就如上文展示的一样,将来,在AI系统的基础上,我们只需要使用非常简单的自然语言,就能设计出以目标为导向的任务,而无需关心它的具体实现,以及它的内部原理。

Python_cocola的博客 2092

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

人工智能核心知识:AI Agent的四种关键设计模式

AI Agent智能工作流在多个行业中展现出实际应用的巨大潜力,这些智能体在编程、研究和多模态任务处理等领域的应用,智能体工作流将在未来几年内极大扩展AI的能力边界。无疑,这四种设计模式的结合使用,不仅能够提升AI Agent在单个任务中的执行能力,还为它在更广泛的应用场景中进行协作和创新提供了可能。AI Agent是指能够在特定环境中自主执行任务的人工智能系统,不仅接收任务,还自主制定和执行工作计划,并在过程中不断自我评估和调整,类似于人类在创造性任务中的思考和修正过程。

百锦再的博客 1万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Agent 到底是什么?它不是会聊天的 AI,而是会执行任务的系统

本文解释 Agent 为什么不是更会聊天的 AI,而是围绕目标、工具、观察、修正和验证构成的执行系统,并说明普通人和工程师该如何理解 Agent 的边界、风险和价值。

ArchAIHarness开源社区创始人。 838

AI Agent爆发前的黎明:Manus不够好,但天快亮了

我们自己定义的agent,它应该在某种意义上像人一样,可以使用各种网页工具,也同时还可以学习怎么使用不同的东西,但这点对Agent来说是非常难的挑战。我们来看看过去几年,AI生态圈是怎么发展推理(Reasoning)、记忆(Memory)和工具(Tools)这三种主要能力的。

m0_59164304的博客 2065

2025核心概念 AI Agent:什么是AI Agent?AI Agent 与大模型的区别_ai agent和大模型的区别

经过以上的分享,大家都直观感受到了AI Agent的重要意义。。DeepSeek,Qwen等强力大模型的出现为AI Agent提供了良好的模型基础,使其可以构建更大更强的智能体系统。AI Agent的发展又让AGI(通用人工智能,旨在创造一个能像人类一样思考、学习、执行多种任务的系统)的美好愿景逐渐成为现实,未来的世界一定是人工智能的世界!

HUANGXIN9898的博客 1372

AI Agent 的本质不是模型,而是流程设计:Harness 才是真正的王牌(实战篇)

AI Agent = LLM 模型 + Harness。模型负责“想”(推理、决策),Harness 负责“做”和“管”——包括任务规划、上下文持久化、工具调用、状态管理、错误恢复、子代理协调,甚至 Human-in-the-loop 介入。模型只是引擎,Harness 才是方向盘、刹车和导航系统。推特上有个开发者说得特别扎心:“模型越来越 commodity(商品化),GPT-5、Claude、Grok 随便换,但 Harness 设计得好坏,直接决定 Agent 能不能活下来。

紫微AI,构建「人格操作系统(ZPF)」 用AI建模人的决策与行为模式 持续记录AI Agent、系统设计与产品实验 1532
上一篇: AI应用岗每天都在干什么:5类真实需求拆解
下一篇: 什么是 Loop Engineering?它和 Harness Engineering 有什么不同?
Llama-Turbo
博客等级 码龄4年 9901粉丝 2024原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值