From af01b01bc2fe61f9f5db8ea5932c496b35959e62 Mon Sep 17 00:00:00 2001 From: likun Date: Wed, 2 Sep 2026 22:48:13 +0800 Subject: [PATCH] revert: remove Log Is the Runtime blogs Reverts apache/maka#4565 and apache/maka#4567. Generated-by: OpenAI Codex --- docs/README.md | 5 - docs/blogs/log-is-the-runtime.md | 264 ------------------------- docs/blogs/log-is-the-runtime.zh-CN.md | 264 ------------------------- 3 files changed, 533 deletions(-) delete mode 100644 docs/blogs/log-is-the-runtime.md delete mode 100644 docs/blogs/log-is-the-runtime.zh-CN.md diff --git a/docs/README.md b/docs/README.md index 89aecc5061..cd8d6bc4f3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,6 @@ This page is the authority map for Maka documentation. Code and contract tests r - Root and package READMEs describe stable product entry points, public seams, and local ownership. - `docs/` contains current cross-cutting architecture, security, product, and validation contracts. -- `docs/blogs/` contains long-form explanations of Maka's design ideas; code and current contracts remain authoritative. - GitHub issues and pull requests contain implementation plans, migration progress, and time-sensitive TODOs. - `docs/archive/` preserves completed plans and superseded material for historical context. Archived documents are not current implementation guidance. @@ -40,10 +39,6 @@ This page is the authority map for Maka documentation. Code and contract tests r - [UI package architecture](../packages/ui/README.md) - [Security policy](../SECURITY.md) -## Blogs - -- [Log Is the Runtime: How Maka Uses an Append-Only Log to Manage Agent State and Context](./blogs/log-is-the-runtime.md) ([中文](./blogs/log-is-the-runtime.zh-CN.md)) - ## Current contracts ### Runtime and Eval diff --git a/docs/blogs/log-is-the-runtime.md b/docs/blogs/log-is-the-runtime.md deleted file mode 100644 index 28547d77b9..0000000000 --- a/docs/blogs/log-is-the-runtime.md +++ /dev/null @@ -1,264 +0,0 @@ - - -[简体中文](./log-is-the-runtime.zh-CN.md) - -# Log Is the Runtime: How Maka Uses an Append-Only Log to Manage Agent State and Context - -## Log Is the Database - -**Log Is the Database** is not a metaphor. It is a data model used by real distributed databases: the state of a database at any point in time can be recomputed from an earlier state and the committed log that follows it. - -In its simplest form: - -```text -State(n) = Apply(State(0), Log[1...n]) -``` - -`Log[1...n]` is the committed log through position `n`, and `Apply` is the database's state transition function. Given the same initial state and the same log entries in the same order, every node should arrive at the same database state. - -Real systems do not replay from the first log entry on every startup. As the log grows, the database periodically takes a snapshot of its complete state at a known log position. Recovery loads that snapshot and replays only the suffix: - -```text -State(n) = Apply(Snapshot(k), Log[k+1...n]) -``` - -A node's local database is therefore not an irreplaceable original. It is closer to a computed result: it may lag, become corrupt, or be deleted entirely. As long as the snapshot and the committed log after it remain available, the node's state can be reconstructed. - -This also determines the write path. A write does not first modify the database and then leave a log entry as an afterthought. It first becomes a log record. Only after that record is replicated and committed does the state machine apply it to the database: - -```text -Client Command - ↓ -Append Log - ↓ -Replicate Log - ↓ -Commit Log - ↓ -Apply to State Machine - ↓ -Update Tables / Indexes / Materialized State -``` - -The authoritative object is not the set of pages currently held by one node. It is the committed log prefix. Tables, indexes, caches, and local storage files are materialized results of applying that prefix. - -This is the most important difference between a log-centric database and the conventional view of a write-ahead log. - -In a traditional database, the WAL is primarily a recovery mechanism. It provides local transaction atomicity and durability. Once changes have safely reached data pages and a checkpoint has completed, older WAL records can usually be reclaimed. In this model, data pages hold the primary state and the log exists to recover them. - -In a log-centric database, the relationship is reversed: - -```text -Traditional WAL: - Data State → Primary - Log → Recovery Record - -Log-centric Database: - Committed Log → Authoritative History - Data State → Materialized Result -``` - -This does not mean the system must retain every entry since genesis forever. Snapshots, checkpoints, and log compaction are still necessary engineering tools. The important question is what defines the evolution of database state—and what determines whether two replicas contain the same database. - -For a log-centric system, the answer is not to compare two sets of local files. It is to ask whether both nodes hold the same committed log and whether they have applied it through the same position. - -Database consistency can therefore be split into two concrete questions: - -```text -Do all replicas agree on the same log? -Does the same log produce the same state? -``` - -Replication and consensus protocols answer the first question. A deterministic state machine answers the second. - -That is the most direct meaning of **Log Is the Database**: the log is not a record left behind after the database runs. The log is the input to database state, and the database we query is the result computed at a particular position in that input. - -## Log Is the Runtime - -Apply the same idea to agents and a similar conclusion follows. - -An LLM does not hold durable, runtime-readable state that advances continuously with a task. Every time the Runtime calls the model, it must tell the model again what the user said, what the model previously did, which tools it called, what those tools returned, and how far the task has progressed. - -The agent's “state” is not hidden inside a model process that stays alive forever. The Runtime reconstructs that state from historical facts before every model call. - -```text -Agent State(t) = Project(RuntimeEvents[0...t], policy, runtime configuration) -``` - -This is how Maka carries **Log Is the Database** into an agent runtime. The Runtime Event Log is the fact space of agent interaction. The state of the agent at a particular moment is a projection of that log under a particular policy. - -Such a log clearly cannot contain only chat text. A real agent execution may look like this: - -```text -1. User: Fix the failing tests in this project -2. Model: Call Grep to search the relevant code -3. Tool: Return the search results -4. Model: Call Read to inspect a file -5. Tool: Return the file contents -6. Model: Call Edit to modify the file -7. Runtime: Request a broader sandbox permission -8. User: Approve -9. Tool: Return the edit result -10. Model: Call Bash to rerun the tests -11. Tool: Return the test results -12. Model: Produce the final answer -13. Runtime: Mark the Run completed -``` - -If we save only entries 1 and 12, we have preserved a conversation, not the execution state of an agent. The agent's next action also depends on tool arguments and results, call/result correlation, permission decisions, terminal status, and the order of all those facts. - -That is why Maka does not represent history as a simple `role + text` sequence. A `RuntimeEvent` may contain: - -```ts -type RuntimeEventContent = - | Text - | Thinking - | FunctionCall - | FunctionResponse - | Error -``` - -An event may also carry actions that affect Runtime control state: - -```ts -type RuntimeEventActions = { - stateDelta?: StateDelta - permissionRequest?: PermissionRequest - permissionDecision?: PermissionDecision - tokenUsage?: TokenUsage - toolDispatch?: ToolDispatch - toolRecovery?: ToolRecovery - endInvocation?: boolean -} -``` - -Every event also carries a `sessionId`, `turnId`, `runId`, and `invocationId`. Together, these identities answer four different questions: which long-running interaction does this belong to, which user-visible turn is this, which concrete execution attempt produced it, and which model/tool invocation contains this activity? - -Events are written to the SQLite `runtime_events` table and receive a monotonically increasing `event_seq` within an invocation. When Maka reads an execution history, it does not load a mutable “current message list.” It reads an immutable prefix through a known position: - -```text -RuntimeEvents[1...highWater] -``` - -The high-water mark matters. It turns “I recovered this execution” into a verifiable statement: recovery must say exactly which event it read through rather than continuing from a vague and potentially changing notion of “latest.” Maka can also digest the immutable prefix, binding a continuation to one exact history instead of to an ambiguous Session. - -The same Runtime Event Log can produce several different kinds of state. - -`projectRuntimeEventsToStoredMessages()` projects the messages, tool activity, and Turn state displayed by the UI. `buildRuntimeEventModelReplayPlan()` projects the model history accepted by the next provider call. `classifyRuntimeEventTerminalFact()` determines whether a Run completed, failed, or was aborted. `buildContinuationReplayPlan()` decides which history can safely be handed to a new Run after a process failure. - -```text - ┌→ Session / UI - │ -Runtime Event Log ───────┼→ Next Model Context - │ - ├→ Run Terminal State - │ - └→ Crash Recovery / Continuation -``` - -These projections may apply different selection rules, but they cannot invent their own facts. - -The model, for example, does not need to see every Runtime control event. The model-history projection skips events marked `modelVisibility: hidden`, does not turn terminal facts into chat messages, and never replays transient streaming chunks. It also pairs function calls with their responses and preserves provider-native semantics such as signed thinking when the provider supports them. - -The UI makes a different selection. It needs to display text, thinking, tool activity, permission state, and errors, but it should not render an internal tool-dispatch recovery fact as a chat message. - -Model context is therefore not history, and neither is the UI transcript. They are two ways of reading the same Runtime Event Log. - -This distinction becomes essential for long-running agents. A model's context window is finite; an agent's execution history can keep growing. Maka must preserve complete history while still allowing the next inference to read a bounded context. - -Replay also needs a precise boundary. - -Replaying the Runtime Event Log does not mean executing every tool again, nor does it reproduce the exact neural state of the model's hidden layers. Maka provides semantic replay: at a known event boundary, it reconstructs the content exchanged by the user and model, tool calls and results, permissions, and terminal state, then uses those facts to build the UI, model context, and recovery decisions. - -If a tool crossed a side-effect boundary but left no result, Maka does not blindly retry it simply because replay found a function call. Tool execution is divided into durable dispatch and outcome boundaries. A crash between them must be classified as unknown or in need of reconciliation. When safety cannot be proven, continuation is blocked rather than assuming that the tool never ran. - -**Log Is the Runtime** does not claim that a log can reproduce the entire physical world. It makes a narrower and more useful guarantee: - -> A process may disappear, the UI may be rebuilt, model context may be selected again, and a new Run may take over execution. But what the agent observed, which calls it made, which results it received, and where execution ended must remain reconstructible from the committed Runtime Event Log. - -For a database, the log replays data state. For Maka, the log replays the state space from which the agent can continue acting. - -## Compaction Is Only a Projection - -Append-only history creates an immediate tension: history keeps growing, but the model's context window does not. - -If context and history are treated as the same thing, there is an obvious response: delete the earliest messages and overwrite them with a summary. The context becomes shorter, but the history has now been rewritten permanently. Recovery, auditing, debugging, or revisiting the task with a larger model can access only the lossy summary. - -Maka separates the two concerns. The Runtime Event Log remains append-only; compaction changes only how the next inference reads it. - -A long history can be projected as a summary of early events followed by recent events in their original form. Older model responses and Tool Results stop consuming tokens in the next inference, but they remain in the source log. Context is compacted; history is not. - -> Context is not history. - -A compaction checkpoint is much like a materialized view in a database. It can be durable and can serve as the fast path for most reads, but it remains a computed result over source data. The authority test is straightforward: if the checkpoint is lost, it can be rebuilt from the log. If the checkpoint disagrees with the log, discard the checkpoint and return to raw history—not the other way around. - -This is why a reliable checkpoint must preserve more than summary text. It must identify the continuous history it covers, the event boundary where that coverage ends, and whether the source still matches the source used to create the summary. Only then can the Runtime know that it is replacing a known prefix with a projection rather than injecting an untraceable summary into model context. - -The continuous prefix is especially important. Compaction does not remove scattered pieces that happen to look unimportant. It draws an explicit waterline in an ordered log: the checkpoint represents everything before the waterline, while events after it remain raw. New facts can continue to append at the tail. The next compaction needs to fold only the previous checkpoint and the newly grown suffix instead of reinterpreting the entire Session. - -Of course, saying “compaction is only a projection” does not mean that compaction has no effect on agent behavior. - -A database index generally should not change query results. An agent summary, however, is inherently lossy. A model given full history may make a different next decision from a model given compacted context. Compaction does not change the past, but it changes the state visible to the model as it generates the future. - -“Only” describes authority, not importance. A checkpoint has no authority to rewrite history, but it participates in determining how history grows next. A new projection should therefore be validated, made durable, and only then given to the model. Generating a summary in memory, sending it to the model, and persisting it afterward creates an unrecoverable fork: after a restart, the Runtime knows what the model produced but cannot know which version of history the model saw. - -Append-only history also has a practical benefit: higher KV-cache prefix hit rates. An agent task may call a model many times. As long as the system prompt, tool definitions, and serialization remain stable, the next request usually appends new model output, Tool Calls, and Tool Results to the previous history. The provider can reuse the long token prefix it has already computed. - -Compaction deliberately breaks the old prefix once, but it also establishes a shorter new prefix anchor. As long as that checkpoint remains stable, subsequent events append after it and the new KV-cache prefix can be reused. Compaction is therefore not continuous context rewriting. It occasionally establishes a new read point for an ever-growing log. - -This is Maka's choice between append-only history and finite context: history always moves forward; compaction does not delete it, but decides where and at what resolution the model continues reading it. - -## Tool Result Prune Is Context Offload - -Even without a long history, a single Tool Call can fill a model's context window. - -Reading a large file, searching an entire repository, running tests, fetching a web page, or waiting for a group of child agents can produce tens or hundreds of thousands of tokens. The model may need those details for one step, but repeatedly carrying the full Tool Result into every later inference is usually expensive and unnecessary. - -Tool Result Prune does not answer “should this history still exist?” It answers “must this large object remain resident in the model's working memory?” - -Maka first writes the complete Tool Result to a separate context-offload store, then replaces the original body in the next model request with a small placeholder. The placeholder retains the producing tool, original size, content digest, and a readable address. The model knows that a complete result exists and how to retrieve it when details become relevant. - -“External” here means external to model context, not necessarily remote. Maka remains local-first: the offload store is a Runtime-managed layer of durable local storage outside the finite context window. - -Pruning is therefore closer to operating-system swap or demand paging than deletion. The context window is expensive working memory, the offload store is larger backing storage, and the placeholder is the page-table entry left in the working set. The difference is that page-in is explicit rather than transparent. The model can inspect the archive's structure and metadata, query one item, or read a bounded page instead of loading the whole object back into context. - -That read path must be bounded. Otherwise a hundred-thousand-token result would be pruned only for one archive read to inject all hundred thousand tokens again. Maka separates inspect, query, and paginated read: first show the model what the archive contains, then retrieve only the portion needed for the current inference. - -The most important ordering rule is simple: archive first, placeholder second. - -A placeholder that says only “result omitted” has no recovery value. The Runtime can remove the original from model context only after the complete body has been written successfully and the reference, content hash, byte count, and Session identity all match. If archival fails, the correct behavior is to keep the complete Tool Result rather than save tokens by creating a dangling pointer. - -Reads cannot blindly trust an address returned by the model either. An archive belongs to the Session that created it, and its contents must match the hash and size stored in the placeholder. This makes the placeholder a capability for one exact object, not a path that can be used to browse arbitrary local data. - -Maka applies this offload at two time scales. - -Within the active Turn, a tool may produce a large result just before the next step. The Runtime can move it out of the active context so that the model sees a reference in the next inference and decides whether to inspect its structure, read a portion, or leave it unloaded. - -During history replay, recent Tool Results remain complete while older oversized results become placeholders. The Session retains a relatively complete recent working set and moves older, less frequently accessed results to a cheaper storage tier. - -Both forms of pruning affect only the provider-visible projection. The original Tool Result remains in the canonical Runtime Event Log, and later history compaction still reads the original event rather than the placeholder. Otherwise the summary would describe “some content was omitted” instead of what the agent actually observed. - -Tool Result Prune and History Compaction both reduce context, but they solve different problems. - -History Compaction folds a continuous span of history into a lower-resolution semantic summary. Tool Result Prune preserves the event structure and moves only an oversized payload out of the hot working set. The first changes the resolution at which the model reads history; the second changes the storage tier of a large object. - -Together they form a tiered memory system for agents. The newest and most relevant facts remain directly in context. Large but potentially useful Tool Results are available by reference and loaded on demand. A checkpoint preserves continuity across older history. The complete execution facts remain in the append-only log. - -That is the central boundary in Maka's context management: context may be compacted, paged, and projected again—but saving tokens should never require pretending that something which happened never happened. diff --git a/docs/blogs/log-is-the-runtime.zh-CN.md b/docs/blogs/log-is-the-runtime.zh-CN.md deleted file mode 100644 index 1466868f92..0000000000 --- a/docs/blogs/log-is-the-runtime.zh-CN.md +++ /dev/null @@ -1,264 +0,0 @@ - - -[ENGLISH](./log-is-the-runtime.md) - -# Log Is the Runtime:Maka 如何用 Append-Only Log 管理 Agent 状态与上下文 - -## Log Is the Database - -所谓 **Log Is the Database**,不是一句比喻,而是一种真实存在于分布式数据库中的数据理念:数据库在任意时刻的状态,都可以由一份历史状态和其后提交的日志重新计算出来。 - -写成公式就是: - -```text -State(n) = Apply(State(0), Log[1...n]) -``` - -`Log[1...n]` 是截至位置 `n` 已经提交的日志,`Apply` 是数据库的状态转换过程。只要初始状态相同,并且按照相同顺序执行相同的日志,节点就应该得到相同的数据库状态。 - -实际系统当然不会在每次启动时从第一条日志开始回放。随着日志增长,数据库会定期生成 snapshot,把某个日志位置上的完整状态保存下来。恢复时先加载 snapshot,再回放它之后的日志: - -```text -State(n) = Apply(Snapshot(k), Log[k+1...n]) -``` - -因此,一个节点的本地数据库并不是不可替代的原件。它更像是一份计算结果:可以落后,可以损坏,也可以被整个删除。只要 snapshot 和后续 committed log 还在,这个节点的状态就能够被重新构造出来。 - -这也决定了写入发生的顺序。一次写入不是先修改数据库,再顺手留下一条日志。它首先成为日志中的一条记录,在被复制并提交之后,才由状态机应用到数据库状态中: - -```text -Client Command - ↓ -Append Log - ↓ -Replicate Log - ↓ -Commit Log - ↓ -Apply to State Machine - ↓ -Update Tables / Indexes / Materialized State -``` - -这里真正具有权威性的不是某个节点此刻持有的数据页,而是已经提交的日志前缀。表、索引、缓存以及本地存储文件,都是这段日志执行后的物化结果。 - -这正是它与传统 WAL 观念最重要的区别。 - -在传统数据库中,WAL 首先是一种恢复机制。数据库通过日志保证本地事务的原子性和持久性;一旦修改已经安全写入数据页并完成 checkpoint,较早的日志就可以被回收。在这个模型里,数据页是主要状态,日志服务于数据页的恢复。 - -而在 log-centric database 中,关系反了过来: - -```text -Traditional WAL: - Data State → Primary - Log → Recovery Record - -Log-centric Database: - Committed Log → Authoritative History - Data State → Materialized Result -``` - -这并不意味着系统必须永久保留从创世开始的每一条日志。Snapshot、checkpoint 和 log compaction 仍然是必要的工程手段。关键在于:数据库用什么来定义状态的演进,又用什么来判断两个副本是否拥有同一个数据库。 - -对于一个以 log 为核心的系统,答案不是比较两份本地文件是否相同,而是检查两个节点是否拥有相同的 committed log,以及它们是否把这段日志应用到了相同的位置。 - -换句话说,数据库的一致性被拆成了两个更具体的问题: - -```text -所有副本是否同意同一段日志? -相同的日志是否会产生相同的状态? -``` - -前一个问题由复制和共识协议解决,后一个问题由确定性的状态机解决。 - -这就是 **Log Is the Database** 最直接的含义:log 不是数据库运行之后留下的记录;log 是数据库状态的输入,而我们查询到的数据库,只是这段输入在某个位置上的计算结果。 - -## Log Is the Runtime - -把这个思路放到 Agent 上,会得到一个很相似的结论。 - -LLM 本身没有一份会随着任务持续推进的、可供 Runtime 读取的持久状态。每次调用模型,Runtime 都要重新告诉它:用户说过什么、模型此前做过什么、调用过哪些工具、工具返回了什么,以及任务目前进行到了哪里。 - -换句话说,Agent 所谓的“状态”,并不藏在某个一直运行的模型进程里。它是 Runtime 根据历史事实,在每次模型调用之前重新构造出来的。 - -```text -Agent State(t) = Project(RuntimeEvents[0...t], policy, runtime configuration) -``` - -这就是 Maka 对 **Log Is the Database** 的进一步使用:Runtime Event Log 是 Agent 交互的事实空间;Agent 在某个时刻的状态,是这段日志在特定策略下的投影。 - -这里的 log 显然不能只记录聊天文本。一次真正的 Agent 执行可能是这样的: - -```text -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 的执行状态。真正决定 Agent 下一步行为的,还包括工具调用的参数和结果、调用之间的对应关系、权限是否获得、执行是否已经结束,以及这些事实之间的顺序。 - -这也是 Maka 没有用一个简单的 `role + text` 结构表示历史的原因。在代码中,`RuntimeEvent` 的内容包括: - -```ts -type RuntimeEventContent = - | Text - | Thinking - | FunctionCall - | FunctionResponse - | Error -``` - -事件还可以携带影响 Runtime 控制状态的 actions,例如: - -```ts -type RuntimeEventActions = { - stateDelta?: StateDelta - permissionRequest?: PermissionRequest - permissionDecision?: PermissionDecision - tokenUsage?: TokenUsage - toolDispatch?: ToolDispatch - toolRecovery?: ToolRecovery - endInvocation?: boolean -} -``` - -每条事件同时带有 `sessionId`、`turnId`、`runId` 和 `invocationId`。这些 ID 分别回答:这段长期交互是谁、这是用户看到的哪一轮、这是哪一次具体执行尝试,以及这组模型和工具活动属于哪次 invocation。 - -事件写入 SQLite 的 `runtime_events` 表,并在一次 invocation 内获得单调递增的 `event_seq`。Maka 读取一段执行历史时,不是读取“当前消息列表”,而是按 `event_seq` 读取一个 immutable prefix: - -```text -RuntimeEvents[1...highWater] -``` - -这里的 `highWater` 很重要。它把“我恢复了这次执行”变成了一个可验证的陈述:恢复逻辑必须明确自己读到了哪一条事件,而不是从一个仍可能变化的“最新状态”继续运行。Maka 还会对这个 immutable prefix 计算 digest,使 continuation 绑定到一段确定的历史,而不是绑定到一个模糊的 Session。 - -同一段 Runtime Event Log 会产生多种不同的状态。 - -`projectRuntimeEventsToStoredMessages()` 将它投影成用户在 UI 中看到的消息、工具活动和 Turn 状态;`buildRuntimeEventModelReplayPlan()` 将它投影成下一次 provider 调用能够接受的模型历史;`classifyRuntimeEventTerminalFact()` 判断一次 Run 最终是 completed、failed 还是 aborted;`buildContinuationReplayPlan()` 则在进程崩溃之后判断哪一段历史可以安全地交给一个新的 Run 继续执行。 - -```text - ┌→ Session / UI - │ -Runtime Event Log ───────┼→ Next Model Context - │ - ├→ Run Terminal State - │ - └→ Crash Recovery / Continuation -``` - -这些 projection 可以有不同的选择规则,但不能各自发明事实。 - -例如,模型不需要看到所有 Runtime 控制事件。`buildRuntimeEventModelReplayPlan()` 会跳过 `modelVisibility: hidden` 的事件,不会把 terminal fact 当作一条对话消息,也不会把流式传输中的临时 chunk 放回模型上下文。它还要重新配对 function call 和 function response,并在 provider 支持时保留 signed thinking 等原生语义。 - -UI 的选择又不同。它需要展示文本、thinking、工具活动、权限状态和错误,但不应该把内部的 tool-dispatch recovery fact 渲染成一条聊天消息。 - -因此,模型上下文不是历史本身,UI transcript 也不是历史本身。它们都是对同一份 Runtime Event Log 的读取方式。 - -这个区分对长时间运行的 Agent 尤其重要。模型的 context window 是有限的,Agent 的执行历史却可以不断增长。如何同时保留完整历史,又让下一次推理只读取一个有限上下文,是 Maka 必须单独解决的问题。 - -这里还必须给“回放”划一条边界。 - -回放 Runtime Event Log,不等于重新执行所有工具,也不等于复现模型当时每一个隐藏层的神经元状态。Maka 当前保证的是 semantic replay:在一个确定的事件边界上,重新得到用户和模型已经交换的内容、工具调用及其结果、权限与终止状态,并据此构造 UI、模型上下文和恢复判断。 - -对于已经越过副作用边界、却没有留下结果的工具调用,Maka 不会因为“回放到了一个 function call”就盲目重试。代码把工具执行拆成 dispatch 和 outcome 两个 durable boundary;如果崩溃发生在两者之间,恢复器必须把它识别为未知或待核对状态。无法证明安全时,continuation 会被阻止,而不是猜测工具没有执行。 - -所以 **Log Is the Runtime** 并不是说 log 可以复刻整个物理世界。它表达的是一个更严格、也更有用的保证: - -> 进程可以消失,UI 可以重建,模型上下文可以重新选择,甚至下一次执行可以由新的 Run 接管;但 Agent 已经观察过什么、做出过什么调用、得到过什么结果,以及执行在哪个边界结束,必须能够从 committed Runtime Event Log 中重新得到。 - -对于数据库,log 回放的是数据状态;对于 Maka,log 回放的是 Agent 可以继续行动的状态空间。 - -## Compaction Is Only a Projection - -Append-only 很自然地带来一个矛盾:历史只会增长,但模型的 context window 不会增长。 - -如果把 context 当成 history,这个问题几乎只有一种解法:删掉早期消息,用一段摘要覆盖它们。上下文是变短了,但历史也被永久改写了。以后无论是恢复、审计、调试,还是换一个更大的模型重新理解任务,能够读到的都只剩那段有损摘要。 - -Maka 选择把两件事分开:Runtime Event Log 继续 append-only;compaction 只决定下一次 inference 如何读取它。 - -一段很长的历史,可以被投影成“早期事件的 summary,加上最近事件的原文”。旧的模型回复和 Tool Result 不再占用下一次推理的 token,但它们仍然留在原始日志中。被压缩的是 context,不是 history。 - -> Context is not history. - -从这个角度看,compaction checkpoint 很像数据库里的 materialized view。它可以被持久化,也可以成为绝大多数读取的快速入口,但它仍然只是某段原始数据的计算结果。判断谁是事实源的方法很简单:checkpoint 丢了,可以从 log 重新生成;如果 checkpoint 与 log 对不上,应该丢弃 checkpoint,退回原始历史,而不是反过来修改 log 迁就 checkpoint。 - -这也是为什么一个可靠的 checkpoint 不能只保存一段 summary。它还必须说明自己覆盖了哪一段连续历史、停在哪个事件边界,以及这段 source 是否仍然和生成 summary 时完全相同。只有这样,Runtime 才能确定自己做的是“用 projection 替换一个已知前缀”,而不是把一段来历不明的摘要塞进模型上下文。 - -这里的“连续前缀”尤其重要。Compaction 不是从历史各处挑选一些看起来不重要的内容删除,而是在一条有序日志上画出一条明确的水位线:水位线之前由 checkpoint 表示,之后仍然保留原始事件。这样,新的事件可以继续追加在尾部;下一次 compaction 也只需要把旧 checkpoint 和新增长的那段历史向前折叠,而不必重新解释整个 Session。 - -当然,“compaction 只是 projection”并不意味着它对 Agent 的行为没有影响。 - -数据库中的索引通常不应该改变查询结果,但 Agent 的 summary 天生是有损的。模型读完整历史和读 compacted context,下一步可能做出不同判断。Compaction 没有改变已经发生的过去,却改变了模型生成未来事件时所能看到的状态。 - -所以这里的“只是”说的是它的 authority,而不是它的重要性:checkpoint 没有资格改写历史,但它会参与决定历史接下来怎样增长。正因如此,一份新的 projection 应该先被验证、再被持久化,最后才交给模型使用。只在内存里临时生成 summary、发送给模型之后才尝试保存,会产生一个无法恢复的分叉:进程重启后,Runtime 知道模型输出了什么,却不知道模型当时究竟看到了哪一版历史。 - -Append-only 还带来另一个很实际的收益:更高的 KV cache 前缀命中率。一次 Agent 任务会连续调用模型很多次;只要 system prompt、tool definitions 和序列化方式保持稳定,下一次请求通常只是在上一轮历史后追加新的 model、Tool Call 和 Tool Result。此前已经计算过的长 token prefix 可以继续被 provider 复用。 - -Compaction 会主动打断一次旧前缀,但它同时建立了一个更短的新前缀锚点。只要这个 checkpoint 保持稳定,后续事件又会继续 append 在它后面,新的 KV cache 前缀便可以持续复用。因此,compaction 不是反复重写上下文,而是偶尔为一条不断增长的日志建立新的读取起点。 - -这就是 Maka 在 append-only 与有限 context 之间做出的选择:历史永远向前追加;compaction 不负责删除历史,只负责决定模型从哪里、以什么分辨率继续阅读历史。 - -## Tool Result Prune Is Context Offload - -即使不考虑很长的历史,一次 Tool Call 也可能瞬间塞满模型的 context window。 - -读取一个大文件、搜索整个代码库、运行测试、抓取网页,或者等待一组子 Agent 返回,都可能产生几万甚至几十万 token 的 Tool Result。模型在拿到结果的那个 step 里也许确实需要这些细节,但在后续每一次推理中重复携带完整结果,通常既昂贵,也没有必要。 - -Tool Result Prune 解决的不是“这段历史还要不要”,而是“这份大对象是否必须一直驻留在模型的工作内存里”。 - -Maka 的选择是先把完整 Tool Result 写入独立的 context-offload storage,再把下一次模型请求中的原文替换成一个很小的 placeholder。Placeholder 保留结果来自哪个工具、原始大小、内容摘要和可回读地址。模型知道这里曾经有一份完整结果,也知道在需要细节时应该怎样取回它。 - -这里的“外部”是相对于模型 context 而言,并不意味着必须上传到远端。Maka 仍然遵循 local-first:offload storage 只是位于有限的 context window 之外,由 Runtime 管理的一层本地持久存储。 - -所以 prune 更像操作系统的 swap 或 demand paging,而不像删除:context window 是昂贵的工作内存,offload store 是容量更大的后备存储,placeholder 是留在工作集里的页表项。区别是这里的 page-in 不是透明发生的,而是由模型显式决定。模型可以先查看 archive 的结构和元信息,再按 item 查询,或者分页读取其中一段,而不必一次把整个大对象重新搬回 context。 - -这条读取路径必须是 bounded 的。否则,一个十万 token 的结果刚被 prune,模型调用一次回读工具又把十万 token 全部塞了回来,context 管理就只完成了一次无意义的往返。Maka 因此把 inspect、query 和分页 read 分开:先让模型知道 archive 里有什么,再只取当前推理真正需要的部分。 - -这里最重要的顺序是:先 archive,再 placeholder。 - -一个只保存“结果已被省略”的占位符没有任何恢复价值。只有当完整内容已经成功写入外部存储,并且引用、内容 hash、字节数和 Session 身份都能够对应起来时,Runtime 才能从模型上下文中移走原文。如果 archive 写入失败,正确的行为是继续保留完整 Tool Result,而不是为了节省 token 制造一个无法回读的空指针。 - -同样,回读也不能只相信模型传回来的地址。Archive 属于产生它的 Session,内容必须与 placeholder 中记录的 hash 和大小一致。这个约束让 placeholder 成为对一份确定内容的 capability,而不是一个可以随意读取本地数据的文件路径。 - -Maka 会在两个时间尺度上做这种 offload。 - -一种发生在当前 Turn 内:工具刚产生一个很大的 Tool Result,在进入下一 step 之前,Runtime 就可以把它从 active context 中卸载。模型在下一次推理中先看到引用,再按任务需要决定查看结构、读取局部,还是完全不加载原文。 - -另一种发生在历史 replay 时:最近的 Tool Result 保持完整,较早且过大的结果在重新构造模型上下文时变成 placeholder。这样,Session 可以保留一段相对完整的近期工作集,而把很少再次访问的旧结果放到更便宜的存储层。 - -这两种 prune 都只作用于 provider-visible projection。Canonical Runtime Event Log 中的原始 Tool Result 仍然存在,后续 history compaction 看到的 source 也仍然是原始事件,而不是 placeholder。否则,summary 就会总结出“这里曾经有一段被省略的内容”,而不是总结 Agent 当时真正观察到的结果。 - -因此,Tool Result Prune 和 History Compaction 虽然都在减少 context,却解决了两个不同的问题。 - -History Compaction 把一段连续历史折叠成更低分辨率的语义摘要;Tool Result Prune 则保留事件结构,只把其中过大的 payload 移出热工作集。前者改变模型阅读历史的分辨率,后者改变一份大对象所在的存储层级。 - -两者共同建立了一种分层的 Agent memory:最新、最相关的事实直接留在 context 中;体积很大但仍可能有用的 Tool Result 通过引用按需读取;更早的历史由 checkpoint 提供连续性的摘要;而完整的执行事实始终保留在 append-only log 中。 - -这也是 Maka 上下文管理最核心的边界:context 可以被压缩,可以被分页,也可以被重新投影;但为了节省 token,不应该假装一件已经发生过的事从未发生。