ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Log Is the Runtime:Maka 如何用 Append-Only Log 管理 Agent 状态与上下文

Log Is the Runtime:Maka 如何用 Append-Only Log 管理 Agent 状态与上下文 Log Is the RuntimeMaka 如何用 Append-Only Log 管理 Agent 状态与上下文【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka导读本篇文章深入解析 Apache MakaIncubating的核心运行时机理——Log Is the Runtime将分布式数据库领域久经考验的 Log Is the Database 思想迁移到 Agent 运行时用一份不可变的RuntimeEvent Log承载 Agent 交互的全部事实再针对 UI、模型上下文、崩溃恢复与任务续跑等不同消费场景生成确定性的投影Projection。读完本文你将理解 Maka 为什么用结构化事件而非role text保存执行历史、如何通过 Compaction 与 Tool Result Prune 在有限的上下文窗口内保留完整事实以及进程崩溃后如何从日志前缀中安全恢复执行。全文以 docs/blogs/log-is-the-runtime.zh-CN.md 为骨架并补充了仓库源码中的实现证据。从 Log Is the Database 谈起构建长时间运行的 Coding Agent 时最棘手的问题往往出在运行时的可靠性上。如果宿主进程意外崩溃正在执行的任务该如何精准恢复如果某个工具在修改文件或发起网络请求途中挂了下次启动还能不能安全重试如果自动化测试一次性吐出数十万 token 的日志怎么避免后续推理的上下文直接被打爆分布式数据库很早就遇到过类似的问题。在数据库领域Log Is the Database是一个经过充分验证的原则数据库在任意时刻的状态本质上都是某份基线状态与其后提交日志的应用结果。State(n) Apply(State(0), Log[1...n])其中Log[1...n]代表截止到位置n已提交的日志序列Apply是状态转移函数。只要初始状态一致并且按相同顺序应用这串日志所有节点就能得到完全一致的数据库状态。生产环境不会每次都从第一条日志开始回放。数据库会定期打 Snapshot 固化全量状态恢复时先加载 Snapshot再回放后面的日志增量State(n) Apply(Snapshot(k), Log[k1...n])在这个模型里节点本地的数据表更像是一份物化缓存可能滞后可能损坏也能直接清空重建。只要 Snapshot 和后续提交的日志还在系统状态就能完整还原。这也决定了数据的写入路径一次变更不会先写磁盘数据页再顺手记条日志而是先追加到日志中。等日志复制并提交后状态机才把它应用到业务数据里Client Command ↓ Append Log ↓ Replicate Log ↓ Commit Log ↓ Apply to State Machine ↓ Update Tables / Indexes / Materialized State系统中的权威数据源始终是已提交的日志前缀表、索引和缓存都是基于日志计算出的物化结果。这和传统单机数据库的 WALWrite-Ahead Logging定位不同。在传统数据库里数据页是主状态WAL 主要是事务原子性和持久性的恢复工具数据页刷盘并完成 checkpoint 之后旧日志就能回收。但在 Log-centric 数据库里日志本身就是权威历史表状态只是日志计算出的投影。Traditional WAL: Data State → Primary Log → Recovery Record Log-centric Database: Committed Log → Authoritative History Data State → Materialized Result副本间的一致性由此拆解为两个确定性的环节共识协议保证所有副本拿到同一份有序日志状态机保证相同的日志输入必定产生相同的应用状态。Log Is the Runtime把这个设计思路放到 Agent 系统中逻辑也是相通的。大语言模型本身是无状态的不会在多次交互中驻留可供 Runtime 随时读取的内部状态。每一次调用模型Runtime 都必须向其组装上下文用户的要求、之前做过的操作、调用了什么工具、工具返回了什么以及当前进行到了哪一步。Agent 的状态并不依附于某个一直在跑的后台进程而是 Runtime 根据历史事实在每次发起推理前动态构建出的投影Agent State(t) Project(RuntimeEvents[0...t], policy, runtime configuration)这就是 Maka 采用Log Is the Runtime的原因RuntimeEvent Log是 Agent 交互的事实空间Agent 在某个时刻的状态则是这份日志在特定策略下的确定性视图。完整的执行日志远比普通的聊天记录复杂它包含任务生命周期的完整步骤1. User: 修复这个项目里失败的测试 2. Model: 调用 Grep 搜索相关代码 3. Tool: 返回搜索结果 4. Model: 调用 Read 读取文件 5. Tool: 返回文件内容 6. Model: 调用 Edit 修改文件 7. Runtime: 请求扩大 sandbox permission 8. User: 批准 9. Tool: 返回修改结果 10. Model: 调用 Bash 重新运行测试 11. Tool: 返回测试结果 12. Model: 输出最终结论 13. Runtime: 将这次 Run 标记为 completed如果只保存第 1 条和第 12 条留下的只是一份聊天文本丢失了执行状态。真正决定 Agent 下一步动作的还包括工具调用的具体参数和返回值、调用与响应的对应关系、权限审批结论以及每一步发生的先后顺序。因此Maka 没有采用简单的role text结构而是定义了结构化的RuntimeEventtype RuntimeEventContent | Text | Thinking | FunctionCall | FunctionResponse | Error事件还可以携带影响 Runtime 控制流的动作type RuntimeEventActions { stateDelta?: StateDelta permissionRequest?: PermissionRequest permissionDecision?: PermissionDecision tokenUsage?: TokenUsage toolDispatch?: ToolDispatch toolRecovery?: ToolRecovery endInvocation?: boolean }源码中的事件契约从仓库源码看这份事件契约在 packages/core/src/runtime-event.ts 中有完整、可判别的类型实现。该文件头部的注释明确指出RuntimeEvent是唯一的内部运行时事实模型the single internal runtime fact model它既不是 UI 事件SessionEvent也不是 trace 行或遥测记录——StoredMessage JSONL、renderer SessionEvent、AgentRunStore 操作行、RunTrace、TelemetryRepo 都属于应从这些事件投影或与其显式关联的视图这与博客所述同一份日志、多种投影的思想完全一致。事件契约还进一步细化了原博客提到的字段可以理解为对文档示例的落地补充Role车道与 Author子系统分离RUNTIME_EVENT_ROLES [user, model, tool, system]决定内容在模型历史中属于哪条车道与 Provider 期望的消息角色一一对应而RUNTIME_EVENT_AUTHORS [user, host, agent, tool, system]表示哪个子系统产生了这条事实agent覆盖模型与流程编排tool覆盖工具执行system覆盖 runner、gate 与 recovery。Role 与 Author 是正交的两个维度是否构成有意义的组合由 Runtime 的策略约束而非类型模块。生命周期状态RUNTIME_EVENT_STATUSES [streaming, completed, failed, aborted, cancelled]其中TERMINAL_RUNTIME_EVENT_STATUSES为后四种终态普通 in-flight 内容事件不携带状态字段streaming是非终结的部分事件如 flow 心跳仍表达生命周期意图但不产生内容增量。模型可见性RUNTIME_EVENT_MODEL_VISIBILITIES [visible, hidden]即博客中modelVisibility: hidden的实现基础——隐藏事件仍完整留存在日志中只是不出现在模型重放的投影里。内容载荷细分除了text / thinking / function_call / function_response / error五类还包括system_noteRuntime 对调用过程的内部记录如上下文被压缩了达到步骤上限turn 被中止它只属于 transcript从不回放给 Providerfunction_call通过id与对应的function_response配对function_response还保留providerOutput供 Provider 原生重放与modelProjection供所有模型历史投影消费的冻结中性内容两份视图正对应博客所述配对函数调用并保留原生思考语义。持久化、前缀边界与多种投影每条事件都打上了sessionId、turnId、runId和invocationId的坐标持久化到 SQLite 的runtime_events表中并在一次 Invocation 内获得单调递增的event_seq。Maka 读取历史时基于event_seq获取一个不可变前缀RuntimeEvents[1...highWater]通过引入highWater边界与前缀摘要Digest恢复逻辑把后续执行绑定在经过校验的历史切片上无需依赖可能发生变化的模糊状态。存储层实现可以在 packages/storage/src/sqlite-runtime-store.ts 中印证对runtime_events表的读取查询统一按ORDER BY event_seq ASC, event_id ASC排序并提供upToEventSeq形式的高水位参数? IS NULL OR event_seq ?从执行记录中读取时还会带出committed_at时间戳。也就是说按单调递增event_seq截取不可变前缀不只是一篇博客的构想而是存储层真实提供的读模型能力。同一份RuntimeEvent Log会针对不同消费场景生成不同的投影┌→ Session / UI │ Runtime Event Log ───────┼→ Next Model Context │ ├→ Run Terminal State │ └→ Crash Recovery / ContinuationprojectRuntimeEventsToStoredMessages()投影为前端展示所需的会话消息、工具状态与轮次信息。实现在 packages/runtime/src/runtime-event-read-model.ts同文件还提供了带归档状态标注的变体projectRuntimeEventsToStoredMessagesWithArchiveStatuses。buildRuntimeEventModelReplayPlan()组装下一次模型调用所需的有效上下文跳过内部控制事件如modelVisibility: hidden和流式临时分块配对函数调用并保留原生思考语义。实现在 packages/runtime/src/model-history.ts源码中可以看到它逐事件判定跳过 partial 事件partial_skipped、跳过显式 hidden 事件model_hidden_skipped、对终态事件仅记录诊断信息terminal_fact_diagnostic_only、跳过 thinking-only / tool-only 步骤的空文本收尾事件empty_text_skipped同时通过collectToolActivityTurnIds汇总整个 ledger 中包含工具活动的 turn避免在预算裁剪后的历史切片中误重放带签名思考的内容。classifyRuntimeEventTerminalFact()判定单次 Run 的终态completed、failed 或 aborted。同样位于 packages/runtime/src/runtime-event-read-model.ts。buildContinuationReplayPlan()在进程退出后判定哪些已确认的历史可以安全移交后续 Run 继续执行。实现在 packages/runtime/src/continuation-replay.ts输入是若干不可变前缀段ImmutableRuntimePrefixV1输出为ContinuationReplayPlanV1包含边界游标boundary、Provider 重放摘要providerReplayDigest与分段计划任何一段包含损坏的工具恢复事实tool_recovery_corruption或未决/停驻的工具恢复事实tool_recovery_unsettled时整个续跑计划都会被判定为blocked而非盲目重放。工具恢复的 Dispatch / Outcome 边界在工具调用的恢复上Maka 将工具执行拆分为落盘派发Dispatch和落盘结果Outcome两个边界。如果崩溃发生在两者之间系统将该状态标记为待核对在无法证明幂等和安全性时阻止自动重试避免静默重试引发重复写文件等副作用。只要 Committed Runtime Event Log 存在进程即便崩溃UI 即便重载模型上下文即便重构Agent 曾经观察到的事实、发起的调用、返回的结果与结束的边界都能从日志中确定性地恢复出来。Compaction作为物化视图的压缩投影日志追加写机制面临一个实际限制事件序列单调递增但模型的上下文窗口大小是固定的。如果把模型上下文等同于执行历史最直接的做法是截断早期记录用模型生成的摘要就地覆盖。这样做会永久破坏底层历史丢失排查线索后续换用更大上下文的模型时也无法再利用当年的完整信息。Maka 将事实层面的历史History与推理层面的上下文Context区分开来RuntimeEvent Log保持 Append-Only 不可变Compaction 只改变下一次模型推理读取历史的方式。较长的历史会被投影为早期事件的摘要加近期事件的原文字节。老旧的中间交互不再占用后续推理的 Token但它们依然完整留在底层日志中。Compaction Checkpoint 相当于数据库里的物化视图是一份为了加速读取而生成的持久化快照。Checkpoint 遗失时可以通过原始日志重新计算若两者产生分歧始终以权威的原始日志为准。因此可靠的 Checkpoint 必须明确记录其覆盖的连续事件区间、终止水位线以及源日志摘要。Compaction 不会在历史中挑拣删除而是在时间线上画出清晰的水位水位线之前由 Checkpoint 承载水位线之后保持原始事件。后续新产生的事件继续在尾部追加下一次压缩也只需增量折叠旧 Checkpoint 与新生成的增量后缀。摘要本身具有信息损耗会直接影响模型对后续动作的判断。Maka 要求投影结果必须先完成校验与落盘才能交给模型使用。如果在内存中生成摘要后直接发给模型再异步持久化一旦发生崩溃系统就无法复原模型当时到底看到了哪一份上下文。保留追加写前缀还能提高 LLM Provider 的 KV-Cache 前缀命中率。只要 System Prompt、工具定义与序列化格式保持稳定多次调用通常只需在尾部追加增量内容复用已有的计算缓存。Compaction 虽然会重置一次旧前缀但也建立了一个紧凑的全新基线后续交互可以继续基于新基线积累 Cache。Tool Result Prune有边界的上下文卸载即使在交互轮次不多的场景下单次工具调用也可能占满上下文。读取大型源码文件、全仓符号检索、拉取测试日志或等待子 Agent 汇聚结果单次输出可能达到数万甚至数十万 Token。模型在当前步骤需要分析这些细节但在后续的多轮交互中如果一直带着这些大体积数据不仅增加开销还会干扰模型的注意力。Tool Result Prune 的目的是避免大体积对象持续滞留在模型的工作内存中。Maka 借鉴了操作系统的按需分页Demand Paging思想在把完整的 Tool Result 写入持久化存储后模型上下文中的原始负载会被替换为轻量级的占位符Placeholder。占位符记录了调用工具名、原始字节大小、内容哈希以及访问凭证。模型获知完整结果已经归档并在需要细节时通过特定方式发起检索。在目前的实现中归档的大对象由通用的ArtifactStore统一承载。为了提供更专门的上下文卸载生命周期管理系统后续计划迁移至独立的 SQLiteContextOffloadStore见 Issue #4071。从仓库现状看这一计划已开始落地ContextOffloadStore的接口定义位于 packages/core/src/context-offload.ts存储层已有对应的 packages/storage/src/sqlite-context-offload-store.ts 实现SqliteContextOffloadStore及其测试如 packages/storage/src/tests/context-offload-store.test.ts、context-offload-snapshot.test.tsruntime-host 侧也在 packages/runtime-host/src/server/execution-composition.ts 通过openInteractiveContextOffloadStoreForWrite打开写会话并创建对应的只读读取器。卸载与读取路径遵循严格的有边界读取Bounded Read控制将查看目录结构Inspect、按项查询Query与分页读取Paginated Read分开。模型可以先探查元数据仅在必要时拉取局部分片避免一次读取又把数十万 Token 全部倒灌回活跃上下文。在时序上Maka 遵循先归档落盘、后生成占位符。只有在完整负载确认写入存储并且哈希与字节校验一致后Runtime 才会将上下文中的原文替换为占位符。若归档失败系统宁可保留完整内容多占一些 Token也不生成可能失效的悬空引用。卸载机制主要应用在两个阶段单轮执行内Active Turn工具刚产生超大结果时在进入下一步推理前将其移出活跃上下文让模型按需读取。历史重放时History Replay近期工具调用保留完整上下文早期的大型工具结果在重构上下文时替换为占位符形成热数据常驻、冷数据下沉的分层设计。这两类裁剪仅作用于对模型可见的 Projection。原始的RuntimeEvent Log中永远保留完整的工具输出后续的 History Compaction 在生成语义摘要时看到的也是真实事件而不是占位符。History Compaction 沿时间轴压缩历史将长事件序列折叠为低分辨率的语义摘要Tool Result Prune 沿空间轴卸载负载保持事件结构的同时将大对象转移到持久存储中。两者以不可变的 Append-Only Log 为基准在减少 Token 占用的同时保证了执行历史的精确与可复原。结语保留事实延迟决定如何读取Maka 的这些设计最终指向同一个原则先把发生过的事情可靠地记录下来再根据不同场景决定如何读取它们。UI、模型上下文、崩溃恢复和任务续跑读取的是同一份RuntimeEvent Log但各自使用不同的 Projection。Compaction 改变历史的表达粒度Tool Result Prune 改变大对象进入上下文的方式它们都不需要改写已经发生的事实。Append-Only 并没有消除系统复杂度而是把复杂度从维护一份不断被覆盖的当前状态转移到了如何从稳定历史构造合适的视图。这样做的代价是日志会持续增长Projection 需要版本和校验归档数据也必须管理生命周期。但它换来的是更清晰的恢复边界、更完整的审计能力以及在模型和上下文策略变化后重新解释历史的可能性。对于 Agent Runtime 来说状态管理的目标并不是把全部历史永远塞进模型而是保留一份完整、可验证的事实记录并在每次推理前构造一份有界且有用的上下文。模型决定下一步做什么Log 则保证 Runtime 始终能够回答此前究竟发生过什么。这就是Log Is the Runtime。【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表