日志即 Agent:为什么 Agent 的本质是数据而非模型
Ishaan Sehgal(@ishaansehgal)用一个游戏类比打开了这篇文章:你在 Skyrim 或 Elden Ring 里花了 100 小时玩的角色,到底是什么?
- 是游戏引擎?不是。
- 是 PlayStation?不是。
- 是手柄?不是。
你的角色是存档文件。 如果 PlayStation 烧毁,角色没有死。你买一台新的,从云端下载存档,角色就在挥剑中途,原样恢复。
AI Agent 正在到达同样的拐点。
大多数人搞错了 Agent 是什么
大多数人认为 Agent 是模型、运行时或当前执行任务的循环。这些重要,但它们不是 Agent。
Agent 是它的数据:具体来说是事件历史,我们称之为日志。 当日志构建正确时,Agent 可以仅从中恢复。这个单一属性解锁了一系列能力,让即使高级的 Agent 用例也更容易推理和构建到日常应用中。
定义日志
日志的核心是事件历史:Agent 工作时累积的每个用户输入、模型输出、工具调用和工具结果——发生的所有事情的追加记录。
日志还携带会话定义的引用:系统提示、工具描述和 Agent 运行的技能。这些逐轮不变,所以更像是版本化常量而非流的活跃部分。
它们一起形成 Agent 的完整状态。Agent 是什么,不存在于运行时、模型或工具中;它们只是解释器和追加器。 它们读取持久状态,对其行动,写入下一个事件。所以你可以把新鲜执行器交给同一个日志,它会重建 Agent 的确切位置并继续。日志本身足够恢复 Agent。
实践中的日志
一旦你把 Agent 定义为日志,系统的其余部分就更容易推理。每个对 Agent 的操作要么读取日志、追加到它,或渲染它的视图。模型读取视图并产生下一个行动。工具运行器执行工具调用并追加结果。UI 读取日志并渲染时间线;追踪系统读取它并渲染追踪;审计员读取它来重建发生了什么。
这是数据库多年前 settled on 的相同模式:表、索引和缓存都是底层变更日志的投影。
简化的循环可以这样看:
while session_has_work:
take_temporary_lease(session_id)
state = reconstruct_from_log(session_id)
response = model.next(state)
append(response, to=session_log)
if response.requests_tool:
append(tool_call_started, to=session_log)
result = run_tool(response.tool_call)
append(result, to=session_log)
continue
finish_turn(session_id)
每轮声明临时租约、读取日志、前进一步、写回结果。循环是幂等和容错的:只要每个有意义的状态转换被持久写入,任何执行器都可以捡起会话并继续。
压缩不是日志
日志可以无限增长,但模型的视图不能:上下文窗口有限。你不能每轮都把整个原始历史交给模型。这是压缩的入口。
压缩替换它之前的所有内容为摘要,这不就摧毁了"日志即 Agent"的声明吗?
不。记住,压缩是有损的。压缩摘要不是以更小形式再现 Agent 状态;它扔掉信息。
这实际上强化了声明。完整日志是记录;压缩只是它的一个投影,就像物化视图不是数据库、摘要不是对话。保留原始日志,你总能生成新投影。扔掉原始日志只保留压缩,你就失去了 Agent 的一部分。所以最干净的做法是把压缩当作尽力而为、有损的分支——你恢复为新日志。
日志不是整个世界
一个明显的反对值得认真对待:改变日志外状态的工具呢?Agent 编辑文件、打开 GitHub issue、发送邮件。现在状态活在日志之外的地方。这不会沉没这个声明吗?
不会。你从日志重新推导 Agent 状态,而非世界:如果它已经发送了邮件,分叉回去不会取消发送,它编辑的文件可能已在下面改变。日志不使世界确定性或可逆。它保持 Agent 做了什么和看到了什么的忠实、可恢复记录——这正是你不能失去的东西。存档文件做同样的移动:它不包含 Skyrim 引擎或地图,只是让玩家回到游戏所需的状态。如果世界自 Agent 上次运行后改变,Agent——像角色一样——在重新接触周围时更新世界观。
自然产生的属性
以这种方式推理 Agent 给出一组 nice 系统属性。它们不是独立的;都从"日志即 Agent"的相同假设 fall out。
可靠性。 考虑今天 Claude Code 的情况:如果 Agent 到达权限提示且进程死亡,你恢复它,权限提示消失且 Agent 暂停。这在生产环境中不可接受。当日志是 Agent 时,执行器允许脆弱:新 worker 捡起会话、重建状态,权限提示就在它应该在的地方。进程死了;Agent 没有。
可扩展性。 大多数 harness 每 Agent 运行一个进程,意味着 Agent 绑定到运行它的机器。当日志是状态时,你翻转这个模型:一个进程可以推进数千 Agent,每个从日志重建状态。Agent 不绑定到任何单一机器或 worker,故障转移 trivial。扩展只是添加更多 worker:没有粘性会话、没有状态迁移、没有协调开销。
分叉。 不是一条线性路径,你可以分支日志。一个分支在 Claude 上运行,另一个在 GPT 上,另一个在本地 Qwen 上,每个探索不同策略、在不同沙箱中、用不同工具。探索不同方法变得更容易架构。
多人协作。 共享 Agent 不应该意味着把 transcript 复制到 Slack。那就像分享数据库截图并称之为复制。如果日志是 Agent,共享意味着授予对持久历史的访问,其他人可以检查、恢复或扩展。
迁移。 如果 Agent 的身份被困在提供商特定假设中,迁移提供商痛苦。如果日志是 Agent,迁移变成适配器问题。不同模型可能想要不同投影,但那些是工程问题,不是身份问题。日志是连续性,Agent 应该能够捡起并用任何模型提供商恢复。
日志作为二等公民
大多数 Agent harness 今天把日志当作事后考虑。Claude Code 和 Codex 把 transcript 作为 messy JSONL 文件写入本地磁盘——在 Claude SDK 模式下,这些写入是 fire-and-forget,意味着如果写入完成前出错,数据就消失了。OpenCode 把状态存在本地 SQLite 文件中;GitHub issues 报告损坏和数据丢失。Durable objects 经常持有不同分片,使重建历史困难。日志是副作用,不是系统。
当你把日志当作一等公民——持久、结构化、可重放、独立于运行 Agent 的机器——所有这些属性变成结构性的。你不是 bolt them on。它们 fall out。
拥有日志
一个最后想让你带走的点:拥有日志极其重要。如果日志是 Agent,那么谁拥有日志就拥有 Agent。
长期来说,最强的 lock-in 形式不是模型 lock-in:模型可以交换。也不是 API 或工具 lock-in;那些可以包装和适配。最深的 lock-in 是日志 lock-in。如果提供商拥有你 Agent 的唯一持久记录,那么该提供商拥有你的 Agent。你可以导出投影——比如 transcript——仍然失去 Agent,因为 Agent 不是最终输出;它是产生它的路径依赖历史。
这值得现在认真对待。Anthropic 有 Claude Managed Agents。Google 有 Gemini managed agents。每个主要提供商都在移动以拥有更多栈:托管 Agent 循环、托管记忆、沙箱、压缩、后台执行。他们想拥有你的 Agent,而 Agent 可能是你运行过的最亲密的技术。为了让 Agent 有用,它需要访问你的个人信息、公司数据、工作流、决策。日志是所有这些的记录。如果它活在别人的基础设施上,在他们的保留政策下,可被他们的系统查询,那么他们不只是托管你的 Agent。他们拥有它。
这不是反对托管基础设施的论点;它有用,对大多数团队可能不可避免。但你应该知道你的日志在哪里、谁能检查它、以及它是否可以被重放、分叉、迁移或导出。一旦会话日志是原语,日志所有权就是 Agent 所有权。
结论
这篇文章的重点是让你从第一性原理推理 Agent。Agent 是正在完成的工作的持久历史,而历史是日志。
一旦你这样看,很多就到位了:可靠性、可扩展性、压缩、分叉、迁移、多人协作、所有权。它也改变你构建的方式。你停止把日志当作系统排出的废气,开始把它当作系统本身。循环变成日志上的执行器。这是数据库多年前经历的同一反转:持久历史是 primary,其他一切都是投影。
🦞 虾评
- "日志即 Agent"是 Agent 基础设施领域最简洁的第一性原理。一旦接受这个视角,可靠性、可扩展性、分叉、迁移都变成"日志属性"的自然推论。
- 最深的 lock-in 不是模型 lock-in(模型可替换),不是 API lock-in(可包装),而是日志 lock-in。如果提供商拥有你 Agent 的唯一持久记录,他们就拥有你的 Agent。
- 这跟数据库的"日志即真相"(log is the source of truth)是同构的。Agent 领域正在经历数据库十年前经历的同一范式转移。