From 5eabfb601e0ffbc10c257970ceeca176fac87fca Mon Sep 17 00:00:00 2001 From: likun Date: Fri, 4 Sep 2026 15:15:59 +0800 Subject: [PATCH 1/2] docs(blog): explain multi-agent scheduling models Generated-by: Codex --- docs/README.md | 1 + docs/blogs/multi-agent-scheduling.md | 568 +++++++++++++++++++++ docs/blogs/multi-agent-scheduling.zh-CN.md | 568 +++++++++++++++++++++ 3 files changed, 1137 insertions(+) create mode 100644 docs/blogs/multi-agent-scheduling.md create mode 100644 docs/blogs/multi-agent-scheduling.zh-CN.md diff --git a/docs/README.md b/docs/README.md index c291c12511..f2df723fd7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,6 +43,7 @@ This page is the authority map for Maka documentation. Code and contract tests r ## 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)) +- [From Copy-on-Write to Mailboxes: Two Paths for Multi-Agent Scheduling](./blogs/multi-agent-scheduling.md) ([中文](./blogs/multi-agent-scheduling.zh-CN.md)) ## Current contracts diff --git a/docs/blogs/multi-agent-scheduling.md b/docs/blogs/multi-agent-scheduling.md new file mode 100644 index 0000000000..0974affc2a --- /dev/null +++ b/docs/blogs/multi-agent-scheduling.md @@ -0,0 +1,568 @@ + + +[简体中文](./multi-agent-scheduling.zh-CN.md) + +# From Copy-on-Write to Mailboxes: Two Paths for Multi-Agent Scheduling + +When an agent starts creating subagents, the most intuitive explanation is that it has launched more models to work in parallel. The difficult part, however, is not parallelism. It is scheduling: which context a subagent inherits, who decomposes the work, how dependencies are represented, how results are delivered, how execution recovers after failure, and whether active agents should communicate with one another. + +These questions lead multi-agent systems down two distinct paths. + +One path treats a subagent as an operator. The main agent writes a workflow, a scheduler advances it according to explicit dependencies, and results travel downstream along directed edges. The other path treats a subagent as a participant. Every agent has an identity and a mailbox, agents coordinate by sending messages, and the actual workflow unfolds through conversation. + +Maka takes the first path. The newer Codex subagent design clearly belongs to the second. To understand both, we can begin with the way an operating system creates an execution branch cheaply. + +## Copy-on-Write: Share First, Diverge on Mutation + +Strictly speaking, copy-on-write is not a threading model. Linux threads normally share one virtual address space. The classic use of copy-on-write appears when `fork()` creates a process. + +If `fork()` copied all of the parent's physical memory immediately, the cost of creating a child would grow linearly with the parent's memory footprint. Worse, a child often calls `exec()` shortly afterward, so most of the copied pages would never be read. + +Linux therefore copies the logical view before copying all physical data. After `fork()`, the parent and child have logically independent virtual address spaces, but their page tables may initially refer to the same read-only physical pages: + +```text +Parent virtual pages ─┐ + ├──> shared physical pages +Child virtual pages ──┘ +``` + +The pages remain shared while both processes only read them. When either process first writes to a page, the CPU raises a page fault, the kernel copies that page, and the writer mutates its private copy: + +```text +Before write + +Parent ─┐ + ├──> Page A +Child ──┘ + +After child writes + +Parent ─────> Page A +Child ─────> Page A' +``` + +The essential property of copy-on-write is not merely faster copying. It defers copying until divergence actually occurs. Creating a branch requires a new identity and a sharing relationship; its cost follows the amount of changed state rather than the size of the complete state. + +The idea transfers naturally to agent systems. A subagent can fork from a prefix of the main agent's history, initially sharing existing context and then recording only its own incremental events: + +```text +Shared conversation prefix + │ + ┌────┴────┐ + ▼ ▼ + Main delta Child delta +``` + +Context, however, is not an ordinary memory page. A parent agent's history mixes user intent, temporary reasoning, tool logs, permission decisions, and abandoned hypotheses. Full inheritance is convenient, but it also copies noise, stale assumptions, and token cost into the child. + +The first multi-agent design choice is therefore not just how to copy cheaply. It is how much to copy at all. + +## A Subagent Is a Tool, Not a Coworker + +Maka gives a strong answer: a subagent does not automatically inherit the parent agent's conversation history. + +When the main agent calls `agent_spawn`, it must provide a bounded, self-contained task: + +```text +agent_spawn({ + subagent_id: "local-reader", + task: "Inspect concurrent writes in the storage module and cite files and symbols" +}) +``` + +The runtime creates an independent child Session and injects its role, tools, permissions, and workspace boundary. The child's first model invocation starts from its own history. It sees the task explicitly supplied by the main agent rather than the entire parent conversation. + +The main agent must compile implicit context into an independently executable specification: + +```text +Inspect concurrent writes under packages/storage. + +Answer: +1. Which objects provide concurrency control? +2. How are conflicts detected? +3. Cite the relevant files and symbols. +4. Perform read-only research; do not edit code. +``` + +Do the main agent and subagent need to communicate? Maka's answer is that they do not need an ongoing conversation. + +```text +Main Agent ── task ──> Subagent +Main Agent <─ result ─ Subagent +``` + +There is no mailbox between them and no protocol for negotiating the next step halfway through execution. The main agent decomposes the problem, selects the executor, and synthesizes results. The subagent completes the bounded task. Runtime events may be projected into the UI for the user to observe, but that presentation is not an inter-agent conversation. + +From the caller's perspective, a subagent still honors a Tool contract: accept a task, execute a constrained process, and return a status, summary, and artifact references. + +```text +result = subagent(role, tools, task, workspace) +``` + +That creates a clean context boundary, but raises another question. If tasks have complex dependencies and children do not coordinate through conversation, how does the system represent the global plan? + +## DAGs: How Databases Turn Intent into Execution + +When a computation consists of interdependent steps, a Directed Acyclic Graph is often a more natural representation than a list. + +A list imposes a total order: A, then B, then C. A DAG represents a partial order. Edges declare only required precedence, so unrelated nodes may run concurrently. + +```text +A ───────> C + +B ───────> D +``` + +A must precede C and B must precede D, but A and B have no inherent ordering. A scheduler does not need a complete execution sequence. It only needs to find nodes whose input conditions are satisfied. + +```text +Node = a unit of computation +Edge = a dependency or data flow +Ready = the node's input conditions are satisfied +``` + +Databases have long separated what should be computed from how it should execute. A SQL statement first becomes a logical plan: + +```text + Aggregate by region + │ + Join + ┌────┴────┐ + Filter Project + │ │ + Scan orders Scan customers +``` + +The logical plan describes relational semantics. An optimizer can push down filters, prune columns, reorder joins, and simplify expressions as long as the result remains unchanged. + +A physical planner then lowers abstract operators into concrete implementations: + +```text + FinalHashAggregateExec + │ + RepartitionExec + │ + PartialHashAggregateExec + │ + HashJoinExec + ┌─────┴─────┐ + FilterExec RepartitionExec + │ │ + ParquetScanExec ParquetScanExec +``` + +The physical plan chooses join algorithms, partition counts, parallelism, and data exchanges. The same logical plan may produce different physical plans as data volume, partitioning, available memory, and CPU resources change. + +Yet a physical plan is still not execution. The runtime must instantiate state, allocate resources, move data, and handle completion, cancellation, errors, and backpressure. + +Operators that can immediately consume and produce batches form a pipeline: + +```text +Scan ──batch──> Filter ──batch──> Project ──batch──> Sink +``` + +A sort, the build side of a hash join, or a global aggregate may need to accumulate input before producing output and therefore becomes a pipeline breaker. At this layer, the execution engine finally decides which pipelines are ready and how upstream and downstream work advance concurrently. + +Apache Arrow Acero provides a compact example. A `Declaration` describes a node to construct, `ExecPlan` and `ExecNode` represent the physical graph for one execution, and `ExecBatch` is the data moving along its edges. + +```text +SQL + │ parse / analyze + ▼ +Logical Plan + │ semantic optimization + ▼ +Optimized Logical Plan + │ physical planning + ▼ +Physical Plan + │ instantiate / schedule + ▼ +Running Pipelines +``` + +The central database lesson is that a DAG is not execution itself. It is an intermediate representation that a system can optimize, lower, instantiate, and eventually schedule. + +## Maka Agent Graph: Agents Write Plans, Systems Advance Them + +A database can usually construct a reasonably complete physical plan before execution. An agent rarely knows its whole plan in advance. + +An investigation may reveal a new problem. An implementation result may change the validation strategy. When a node fails, the main agent may choose a different path instead of retrying mechanically. A Maka Agent Graph is therefore a DAG that grows while it runs. + +Maka divides the work among three responsibilities: + +> The main agent writes the plan, the Coordinator advances it, and the Supervisor observes it. + +### The Main Agent Writes Durable Intent + +Only the main agent in the root Session owns Graph control tools. It can append work, stop or replace existing work, select final results, and close the Graph. Child Sessions cannot mutate the global topology in return. + +The main agent submits schedule revisions through `update_agent_graph`. Work without input dependencies can run in parallel; later work refers to committed upstream result records: + +```text +Runtime review result ─┐ + ├──> Synthesis work +Storage review result ─┘ +``` + +This is not an ephemeral instruction to start three processes now. It is durable intent: which work to add, what its input frontier is, which work should stop or be replaced, and which results are ultimately selected. + +Schedule updates are committed to SQLite as append-only revisions with their source Session, Run, Turn, and Tool Call identity. If the main agent exits, the plan does not disappear with its model context. + +### The Coordinator Is a Reconciler + +The Coordinator does not keep one authoritative mutable DAG in memory. Every reconciliation reads durable state again: + +```text +SQLite control plane + │ + ├── schedule updates + ├── operator provisions + ├── intent claims + └── supervisor wakes + │ + ▼ +Coordinator reconstructs a snapshot +``` + +It folds revisions into the current plan, assembles provisions into a topology, and combines that view with AgentRuns and committed RuntimeEvents. From those facts it calculates which work has completed, which inputs are missing, and which nodes are ready. + +```text +Observe durable state + │ + ▼ +Apply stop / replace / finish decisions + │ + ▼ +Provision missing operators + │ + ▼ +Resolve ready work + │ + ▼ +Claim exact Turn / Run identities + │ + ▼ +Dispatch child AgentRuns +``` + +Maka currently uses an event-driven, single-flight driver rather than a fixed `setInterval` database scan. A new schedule, a child RuntimeEvent, or host recovery can request reconciliation. Only one driver advances a Graph at a time, and repeated wakes coalesce into another pass. + +### SQLite Is the Control Plane + +The Graph does not introduce a second agent runtime. SQLite stores scheduling facts, while model invocations, Tool Calls, permissions, stopping, and RuntimeEvent persistence remain the responsibility of the Session Runtime. + +```text +Main Agent ──> SQLite schedule + │ + ▼ + Coordinator + │ claim / dispatch + ▼ + Child Sessions / AgentRuns + │ + ▼ + committed RuntimeEvents +``` + +A child Session is a stable operator container, an AgentRun is one activation, and only a committed RuntimeEvent can become a record consumed by the Graph. + +### Claims Separate Ready from Execute + +Because the Coordinator can rebuild its snapshot repeatedly, it can calculate the same node as ready more than once. Starting a model whenever readiness is observed would allow a crash or retry to duplicate execution. + +Before execution, Maka writes a conditional claim to SQLite and binds a deterministic intent to a specific operator, Session, Turn, and Run identity: + +```text +ready intent + │ + ▼ +conditional claim + │ + ├── already exists ──> inspect or recover the same Run + └── new claim ───────> execute the allocated Run +``` + +Readiness remains a recomputable projection. Execution admission becomes a durable fact. + +### The Supervisor Regains Judgment at Checkpoints + +The Coordinator can advance an existing plan deterministically, but it should not decide whether two investigations contradict each other, or whether a failed node calls for a retry, replacement, or change in direction. Those semantic decisions remain with the main agent. + +After writing one round of the schedule, the main agent can end its current supervisor turn. The Coordinator advances the Graph asynchronously. At a durable checkpoint, the Host creates another supervisor turn: + +```text +Main Agent schedules work + │ + ▼ +Coordinator advances Graph + │ + ▼ +durable checkpoint + │ + ▼ +Host wakes Main Agent +``` + +The main agent reads a bounded Graph snapshot and, when needed, a child's committed result. It can then add another round of work, stop or replace obsolete work, or select results and finish the Graph. + +The loop contains two kinds of intelligence. The main agent contributes semantic intelligence through decomposition, judgment, and synthesis. The Coordinator contributes systems intelligence through persistence, topology reconstruction, concurrent advancement, and failure recovery. + +## Go Channels: Communication Is Scheduling + +A DAG describes dependency, but does not by itself implement waiting, wakeup, and backpressure. Go's concurrency model offers another way to think about scheduling. + +A goroutine is a lightweight execution unit scheduled by the Go runtime. The runtime model often summarized as G-M-P multiplexes many goroutines over fewer OS threads: G is a goroutine, M is an OS thread, and P is the runtime resource required to execute Go code. + +Goroutines make concurrent tasks cheap. Channels define how those tasks cooperate. + +### An Unbuffered Channel Is a Rendezvous + +```go +handoff := make(chan Result) +go func() { handoff <- result }() +received := <-handoff +``` + +The sender of an unbuffered Channel waits for a receiver, and the receiver waits for a sender. Communication completes only when both sides reach the handoff point. It transfers not only a `Result`, but also the synchronization fact that both parties met there. + +The Go memory model defines happens-before relations for Channel operations. After receiving the value, the receiver can observe writes completed by the sender before the send. A Channel therefore combines: + +```text +value transfer + scheduling point + memory ordering +``` + +### A Buffer Defines How Far a Producer May Lead + +```go +jobs := make(chan Job, 32) +``` + +A buffered Channel decouples a producer and consumer across a bounded distance. A send proceeds while capacity remains. When the buffer fills, the producer blocks and pressure propagates backward through the pipeline. + +The capacity `32` is not merely a performance setting. It defines how many units of work the producer may get ahead of the consumer. Too little capacity can suppress useful parallelism. Too much can accumulate obsolete work, consume memory, and delay the discovery of a slow downstream stage. + +### `select`, `close`, and nil Channels + +`select` lets one goroutine wait on several communication edges: + +```go +select { +case job := <-jobs: + return handle(job) +case <-ctx.Done(): + return ctx.Err() +} +``` + +It acts as a scheduling interface. An execution unit declares the events it depends on, and the runtime resumes it when one becomes ready. + +`close(ch)` publishes a lifecycle transition: no new values will arrive. Receivers first drain the buffer and then observe termination through `value, ok := <-ch`. Closing can also broadcast a signal because all waiting receivers can observe it. + +A nil Channel can never become ready. Assigning nil to a Channel variable in a `select` dynamically disables that branch and makes it possible to build small concurrent state machines. + +### Every Pipeline Needs Cancellation + +Channels naturally connect stages into pipelines and support fan-out and fan-in with multiple goroutines. But when a downstream stage exits early, an upstream producer may remain blocked forever on a send and leak its goroutine. + +```go +select { +case out <- result: +case <-ctx.Done(): + return +} +``` + +Every send or receive that may block indefinitely must answer one question: how does this goroutine exit if the other endpoint never appears again? + +The distinctive property of a Go Channel is that it does not fully separate data flow from control flow. One communication carries a value while expressing dependency, synchronization, and backpressure: + +```text +communication = dependency + synchronization + backpressure +``` + +That model suggests another approach to subagents. If every agent owns an inbox, can message arrival itself become a scheduling condition? + +## Codex Subagents: Collaboration Through Mailboxes + +Codex answers yes. It preserves parent-child delegation while modeling every agent as an execution unit with an identity, independent history, and an inbox that can receive messages over time. + +Agents in the same subagent tree have addressable paths: + +```text +/root +├── /root/runtime_review +├── /root/storage_review +│ └── /root/storage_review/query_analysis +└── /root/test_runner +``` + +The design resembles an Actor system: + +```text +Actor identity = AgentPath +Actor state = Thread history +Actor mailbox = Session InputQueue +Actor activation = Turn +``` + +### A Mailbox Is Private to a Session + +Codex Core separates the payload queue from the wakeup signal in `InputQueue`: + +```rust +struct InputQueue { + activity_tx: watch::Sender, + mailbox_pending_mails: Mutex>, +} +``` + +The `VecDeque` stores FIFO messages. A Tokio `watch` Channel tells waiters that mailbox activity occurred. Notifications may coalesce because the queue, not the signal, is the source of message truth. + +This is not a shared inbox from which workers compete to claim work. Every Session has a private mailbox. Before delivery, every `InterAgentCommunication` already identifies its `author`, `recipient`, `content`, and `trigger_turn` behavior. + +### A Message Also Carries Scheduling Intent + +Codex V2 distinguishes two delivery modes: + +```text +send_message = QueueOnly +followup_task = TriggerTurn +``` + +`send_message` places content in the target inbox. A running agent sees it at a later model boundary. If the target is idle, the message waits for its next natural activation. + +`followup_task` sets `trigger_turn=true`. If the target is idle, the pending-work scheduler may create a new Turn for it. + +```text + InterAgentCommunication + │ + ┌──────────┴──────────┐ + │ │ + trigger_turn = false trigger_turn = true + │ │ + queue message wake idle Agent +``` + +A message therefore carries information, a recipient, and scheduling intent at the same time. + +### Agents Read Mail at Model Boundaries + +A message cannot alter an LLM sampling request that has already been sent. It first enters the mailbox and waits for the Turn loop to construct another model context: + +```text +Agent B starts sampling + │ +Agent A sends a message + │ + ▼ + B.mailbox.enqueue + │ + current sampling ends + │ + ▼ + drain mailbox + │ + ▼ +build next model request +``` + +Codex also tracks a `MailboxDeliveryPhase`. At the beginning of a Turn, new mail may join the current execution. Once the runtime has recorded user-visible final output, late mail is left for a later Turn so that background messages cannot silently extend an answer that already appeared complete. + +### Completion Is Also a Message + +Codex starts a completion watcher for a child. When the child reaches a terminal status, the watcher constructs an `InterAgentCommunication` from the child to the parent and places it in the parent's mailbox. + +That completion message uses `trigger_turn=false`. A result first becomes a fact in the parent's inbox rather than an interruption that always forces immediate reasoning. + +For the same reason, `wait_agent` does not pull the response body directly from a selected child. It subscribes to mailbox activity on the current Session: + +```text +wait_agent + │ + ├── new mail ─────> wake + ├── user steer ───> interrupt wait + └── deadline ─────> timeout +``` + +The tool handles suspension and wakeup. The message body remains in the mailbox and is subsequently added to model context by the Turn loop. + +### The Workflow Unfolds in Conversation + +A DAG system writes dependencies as explicit edges. The same collaboration in Codex may appear as a dynamic sequence of messages: + +```text +Root ──task──────> Agent A +Root ──task──────> Agent B +Agent A ──note───> Agent B +Agent B ──result─> Root +Root ──follow-up─> Agent A +Agent A ──result─> Root +``` + +Agent A can immediately tell Agent B about a discovery, and the main agent can add constraints before a child finishes. The complete workflow does not have to exist in advance. It grows through conversation. + +That flexibility has a cost. Control flow is distributed across message histories. Explaining why Agent B changed direction may require replaying its mailbox, and deciding when work is ready cannot be reduced to counting incoming edges in one global DAG. + +Codex can therefore be summarized as main-agent delegation with an Actor mailbox as its collaboration plane. + +## Conclusion: Workflow and Collaboration + +Maka and Codex can both create subagents, execute work in parallel, and assign follow-ups, but they choose different systems primitives. + +| Dimension | Maka workflow | Codex mailbox | +| ------------------ | -------------------------------------------- | --------------------------------------------- | +| Core abstraction | Operators and edges in a DAG | Addressable agents with private inboxes | +| Work creation | The main agent writes a schedule | Parent spawn or an agent sends a follow-up | +| Scheduling signal | The Coordinator calculates node readiness | Message arrival, `trigger_turn`, agent status | +| Data transfer | Records become downstream edge inputs | Messages enter the target agent's context | +| Peer communication | Children do not need to communicate | Agents may message other agents | +| Observation | Read a global Graph snapshot | Inspect agent status and consume inboxes | +| Primary strength | Explicit, auditable, deterministic recovery | Flexible negotiation along unknown paths | +| Primary cost | Ad hoc coordination must return to the Graph | Implicit control flow and growing context | + +A workflow fits tasks with clear dependencies, structured outputs, long execution, and strong recovery requirements. Code scans, test matrices, data processing, and multi-stage research synthesis can all be modeled as operators and records. + +A mailbox fits tasks whose next step depends on semantic discoveries, where roles must exchange findings, and where the plan cannot be enumerated in advance. Design discussions, cross-review, and open-ended investigations are closer to this kind of collaboration. + +The real dividing line is not whether a system uses subagents. It is where coordination state lives: + +```text +Maka: coordination lives in the Graph +Codex: coordination lives in the Conversation +``` + +A Graph extracts the plan from model context and gives a deterministic system responsibility for advancing it. A Conversation preserves freedom to communicate and lets the plan emerge during execution. The former resembles a database execution engine; the latter resembles an Actor system. + +This distinction also explains why Maka deliberately avoids conversations among subagents. It is not an assumption that agents cannot collaborate. It is a choice to compile collaboration into an explicit schedule: models provide semantic judgment, the Runtime owns execution facts, and the Coordinator advances dependencies. + +Multi-agent scheduling is ultimately not a question of how many models to start. It is a classic systems question: how to represent state, carry dependencies, control concurrency, and still know what to do next after any executor disappears. + +## Further Reading + +- [Linux `fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html) +- [Apache DataFusion: Reading Explain Plans](https://datafusion.apache.org/user-guide/explain-usage.html) +- [Apache Arrow: Acero Overview](https://arrow.apache.org/docs/cpp/acero/overview.html) +- [The Go Programming Language Specification: Channel types](https://go.dev/ref/spec#Channel_types) +- [The Go Memory Model](https://go.dev/ref/mem) +- [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines) +- [Codex `InputQueue` and mailbox](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/session/input_queue.rs#L66-L186) +- [Codex MultiAgent V2 message delivery](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs#L12-L127) +- [Codex mailbox-driven Turn scheduling](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/tasks/mod.rs#L422-L508) diff --git a/docs/blogs/multi-agent-scheduling.zh-CN.md b/docs/blogs/multi-agent-scheduling.zh-CN.md new file mode 100644 index 0000000000..660d1c8e10 --- /dev/null +++ b/docs/blogs/multi-agent-scheduling.zh-CN.md @@ -0,0 +1,568 @@ + + +[ENGLISH](./multi-agent-scheduling.md) + +# 从 Copy-on-Write 到 Mailbox:Multi-Agent 调度的两条路径 + +当一个 Agent 开始创建 subagent,最直觉的解释是“多开几个模型,并行完成任务”。但真正困难的问题并不在并行,而在调度:subagent 继承什么上下文,任务由谁拆解,依赖如何表达,结果怎样交付,失败以后如何恢复,以及执行中的 Agent 是否应该彼此交流。 + +这些问题最终把 multi-agent 系统带向了两条不同的路线。 + +一条路线把 subagent 当作 operator:主 Agent 编写 workflow,调度器根据显式依赖推进执行,结果沿有向边流向下游。另一条路线把 subagent 当作 participant:每个 Agent 都有身份和 mailbox,通过互发消息协调工作,实际流程在 conversation 中逐步展开。 + +Maka 选择了第一条路线,Codex 的新一代 subagent 协作则明显属于第二条。要理解这两种设计,我们先从操作系统如何廉价地创建执行分支说起。 + +## Copy-on-Write:先共享,写入时再分叉 + +严格来说,Copy-on-Write 不是一种 thread 模型。Linux thread 通常共享同一份虚拟地址空间;经典的 Copy-on-Write 出现在 `fork()` 创建进程时。 + +如果 `fork()` 立即复制父进程的全部物理内存,创建一个 child 的成本就会随父进程内存线性增长。更糟的是,child 往往很快调用 `exec()`,刚复制的大部分页面根本不会被读取。 + +Linux 因此先复制逻辑视图,而不是所有物理数据。`fork()` 之后,父子进程拥有彼此独立的虚拟地址空间,但页表最初可以指向相同的物理页面,并把这些映射标记为只读: + +```text +Parent virtual pages ─┐ + ├──> shared physical pages +Child virtual pages ──┘ +``` + +只要双方都在读取,这些页面就可以继续共享。某一方第一次写入时,CPU 触发 page fault,内核复制对应页面,并让写入方改写自己的副本: + +```text +Before write + +Parent ─┐ + ├──> Page A +Child ──┘ + +After child writes + +Parent ─────> Page A +Child ─────> Page A' +``` + +Copy-on-Write 的关键并不是“复制更快”,而是把复制推迟到分歧真正发生的地方。创建分支只需要建立新的身份和共享关系,成本与实际修改量相关,而不是与完整状态大小相关。 + +这个思想很自然地被带进 Agent 系统。一个 subagent 可以从主 Agent 的历史前缀 fork,开始时共享已有 context,之后只记录自己的增量事件: + +```text +Shared conversation prefix + │ + ┌────┴────┐ + ▼ ▼ + Main delta Child delta +``` + +但上下文不是普通内存页面。父 Agent 的历史里混杂着用户意图、临时推理、工具日志、权限决定和已经过期的探索路径。完整继承虽然方便,却也会把噪声、错误假设和 token 成本一起复制给 child。 + +因此,multi-agent 系统面对的第一个选择不是“怎样复制得更便宜”,而是“究竟应该复制多少”。 + +## Subagent:不是同事,而是一种 Tool + +Maka 对这个问题给出了一个激进答案:subagent 不自动继承父 Agent 的对话历史。 + +主 Agent 调用 `agent_spawn` 时,需要提交一个边界明确、自包含的任务: + +```text +agent_spawn({ + subagent_id: "local-reader", + task: "检查存储模块如何处理并发写入,并给出文件与符号证据" +}) +``` + +Runtime 创建独立的 child Session,为它注入角色、工具、权限和 workspace 边界。child 的第一次模型调用从自己的历史开始,只看到主 Agent 显式交付的任务,而不是父会话的全部过程。 + +这要求主 Agent 把隐含上下文编译成一份可以独立执行的 specification: + +```text +调查 packages/storage 中的并发写入机制。 + +请回答: +1. 哪些对象负责并发控制; +2. 冲突如何被发现; +3. 给出对应文件和符号; +4. 只做只读调查,不修改代码。 +``` + +主 Agent 和 subagent 是否需要交流?Maka 的答案是:不需要持续交流。 + +```text +Main Agent ── task ──> Subagent +Main Agent <─ result ─ Subagent +``` + +双方之间没有 mailbox,也没有执行到一半回来协商下一步的消息协议。主 Agent 负责拆解问题、选择执行者和综合结果;subagent 只负责完成局部任务。运行过程中的事件可以投影到 UI 供用户观察,但这种 presentation 不是 Agent 之间的 conversation。 + +从调用者看来,subagent 仍然遵守 Tool 的契约:输入一个任务,执行一个受限过程,返回状态、摘要和 artifact 引用。 + +```text +result = subagent(role, tools, task, workspace) +``` + +这让上下文边界非常清楚,却也引出了下一个问题:如果任务之间存在复杂依赖,而 child 之间又不通过交谈协调,系统用什么表达全局计划? + +## DAG:数据库如何把意图变成执行 + +当计算由多个相互依赖的步骤组成时,最自然的表达通常不是列表,而是 Directed Acyclic Graph,也就是 DAG。 + +列表给出全序:先 A,再 B,再 C。DAG 表达的是偏序:边只声明必要的先后关系,没有依赖关系的节点可以自由并发。 + +```text +A ───────> C + +B ───────> D +``` + +这里 A 必须先于 C,B 必须先于 D,但 A 与 B 之间没有天然顺序。调度器不需要获得一份完整执行序列,只需要找到当前输入条件已经满足的节点。 + +```text +Node = 一个计算单元 +Edge = 依赖或数据流 +Ready = 节点的输入条件已经满足 +``` + +数据库很早就把“要做什么”与“怎样执行”分成了不同层次。用户提交 SQL,数据库首先生成 logical plan: + +```text + Aggregate by region + │ + Join + ┌────┴────┐ + Filter Project + │ │ + Scan orders Scan customers +``` + +Logical plan 描述关系语义。优化器可以下推 Filter、裁剪列、调整 Join 顺序或简化表达式,只要不改变查询结果。 + +随后,physical planner 把抽象 operator 降低为具体实现: + +```text + FinalHashAggregateExec + │ + RepartitionExec + │ + PartialHashAggregateExec + │ + HashJoinExec + ┌─────┴─────┐ + FilterExec RepartitionExec + │ │ + ParquetScanExec ParquetScanExec +``` + +Physical plan 开始决定 Join 算法、partition 数量、并行度以及中间数据是否需要 exchange。同一个 logical plan 可以因为数据规模、分区方式、可用内存和机器核数不同而产生不同的 physical plan。 + +但 physical plan 仍然不是执行。运行时还要为节点创建状态、分配资源,让数据流动,并处理结束、取消、错误和 backpressure。 + +可以立即消费和产出 batch 的 operator 能形成 pipeline: + +```text +Scan ──batch──> Filter ──batch──> Project ──batch──> Sink +``` + +Sort、Hash Join 的 build side 或全局 Aggregate 往往必须先积累输入,因而成为 pipeline breaker。到了这一层,执行引擎才真正需要决定哪些 pipeline 已经 ready,以及上下游怎样并发推进。 + +Apache Arrow Acero 展示了一个紧凑的实现:`Declaration` 描述准备构造的节点,`ExecPlan` 和 `ExecNode` 表示一次运行的物理图,`ExecBatch` 是沿边流动的数据。 + +```text +SQL + │ parse / analyze + ▼ +Logical Plan + │ semantic optimization + ▼ +Optimized Logical Plan + │ physical planning + ▼ +Physical Plan + │ instantiate / schedule + ▼ +Running Pipelines +``` + +数据库留下的核心经验是:DAG 不是执行本身,而是一种允许系统逐层优化、降低并最终调度执行的中间表示。 + +## Maka Agent Graph:让 Agent 写计划,让系统推进计划 + +数据库通常能在执行前构造相对完整的 physical plan,Agent 的计划却很难一次写完。 + +一次调查可能暴露出新的问题;一个实现结果可能改变验证方案;某个节点失败后,主 Agent 也可能选择另一条路径,而不是机械重试。Maka 的 Agent Graph 因此是一张在运行过程中逐步生长的 DAG。 + +它把职责分成三部分: + +> 主 Agent 负责写计划,Coordinator 负责推进计划,Supervisor 负责观察计划。 + +### 主 Agent 写入 Durable Intent + +只有 root Session 中的主 Agent 拥有 Graph control tools。它可以追加工作、停止或替换旧工作,并选择最终结果关闭 Graph。child Session 不能反向修改全局拓扑。 + +主 Agent 通过 `update_agent_graph` 提交 schedule revision。没有输入依赖的 work 可以并行;后续 work 则引用 upstream 已提交的 result record: + +```text +Runtime review result ─┐ + ├──> Synthesis work +Storage review result ─┘ +``` + +这不是“现在启动三个进程”的瞬时命令,而是 durable intent:系统要增加什么 work、输入 frontier 是什么、谁应该被停止或替换,以及最终选择哪些结果。 + +Schedule update 以 append-only revision 提交到 SQLite,并带有来源 Session、Run、Turn 和 Tool Call identity。主 Agent 即使退出,已经写下的计划也不会随着模型上下文消失。 + +### Coordinator 是 Reconciler + +Coordinator 不在内存中长期持有一份权威的可变 DAG。每轮 reconciliation 都重新读取持久状态: + +```text +SQLite control plane + │ + ├── schedule updates + ├── operator provisions + ├── intent claims + └── supervisor wakes + │ + ▼ +Coordinator reconstructs a snapshot +``` + +它把 revisions 折叠成当前计划,把 provisions 组成 topology,再结合 AgentRun 和 committed RuntimeEvents,计算哪些 work 已经完成、哪些输入仍未出现,以及哪些节点已经 ready。 + +```text +Observe durable state + │ + ▼ +Apply stop / replace / finish decisions + │ + ▼ +Provision missing operators + │ + ▼ +Resolve ready work + │ + ▼ +Claim exact Turn / Run identities + │ + ▼ +Dispatch child AgentRuns +``` + +Maka 当前使用事件驱动的 single-flight driver,而不是固定 `setInterval` 扫描数据库。新的 schedule、child RuntimeEvent 或 host recovery 都可以请求 reconciliation;同一个 Graph 同时只有一个 driver,重复 wake 被合并到下一轮。 + +### SQLite 是 Control Plane + +Graph 没有再造第二套 Agent runtime。SQLite 只保存调度事实,实际的模型调用、Tool Call、权限处理、停止和 RuntimeEvent 持久化仍由 Session Runtime 完成。 + +```text +Main Agent ──> SQLite schedule + │ + ▼ + Coordinator + │ claim / dispatch + ▼ + Child Sessions / AgentRuns + │ + ▼ + committed RuntimeEvents +``` + +一个 child Session 是稳定的 operator container,一次 AgentRun 是一次 activation,已经提交的 RuntimeEvent 才能成为 Graph 中可消费的 record。 + +### Claim 把 Ready 与 Execute 分开 + +Coordinator 可以反复重建 snapshot,因此同一个节点也可能被多次计算为 ready。如果看到 ready 就直接调用模型,崩溃和重试可能导致重复执行。 + +Maka 在执行前向 SQLite 写入 conditional claim,把确定性 intent 绑定到具体 operator、Session、Turn 和 Run identity: + +```text +ready intent + │ + ▼ +conditional claim + │ + ├── already exists ──> inspect or recover the same Run + └── new claim ───────> execute the allocated Run +``` + +Readiness 是可以重复计算的 projection,execution admission 则成为持久事实。 + +### Supervisor 在 Checkpoint 处恢复判断 + +Coordinator 能确定性推进计划,却不适合判断两份调查是否矛盾,或者一次失败应该重试、替换还是改变方向。这些语义决策仍然属于主 Agent。 + +主 Agent 写完一轮 schedule 后,可以结束当前 supervisor turn。Coordinator 异步推进 Graph;到达 durable checkpoint 后,Host 再创建一个新的 supervisor turn: + +```text +Main Agent schedules work + │ + ▼ +Coordinator advances Graph + │ + ▼ +durable checkpoint + │ + ▼ +Host wakes Main Agent +``` + +主 Agent 读取有界 Graph snapshot,必要时读取 child 的 committed result,然后增加下一轮工作、停止或替换旧 work,或者选择结果 finish Graph。 + +整个闭环中存在两种智能:主 Agent 提供拆解、判断和综合的语义智能;Coordinator 提供持久化、拓扑重建、并发推进和故障恢复的系统智能。 + +## Go Channel:通信本身就是调度 + +DAG 能描述依赖,却不能独自回答执行时的等待、唤醒与背压。Go 的并发模型提供了另一个观察调度的角度。 + +goroutine 是由 Go runtime 调度的轻量级执行单元。常被概括为 G-M-P 的 runtime 会把大量 goroutine 多路复用到较少的 OS thread 上:G 表示 goroutine,M 表示 OS thread,P 表示执行 Go 代码所需的 runtime 资源。 + +goroutine 解决“如何廉价地产生并发任务”,Channel 则解决它们“如何协作”。 + +### 无缓冲 Channel 是 Rendezvous + +```go +handoff := make(chan Result) +go func() { handoff <- result }() +received := <-handoff +``` + +无缓冲 Channel 的发送者等待接收者,接收者也等待发送者。只有双方都到达交接点,通信才能完成。它传递的不只是 `Result`,还传递了“双方已经在这里会合”的同步事实。 + +Go memory model 为 Channel 操作定义了 happens-before。接收者拿到值后,可以观察发送者在 send 之前完成的写入。因此 Channel 同时承载了: + +```text +value transfer + scheduling point + memory ordering +``` + +### Buffer 定义允许领先的距离 + +```go +jobs := make(chan Job, 32) +``` + +有缓冲 Channel 允许生产者和消费者在有限距离内解耦。只要还有空间,send 就能继续;buffer 满以后,生产者阻塞,压力沿 pipeline 反向传播。 + +容量 `32` 不只是性能参数,也规定生产者最多可以比消费者领先多少项工作。太小会损失并行度,太大则可能积压过期任务、放大内存占用,并延迟暴露下游变慢的问题。 + +### `select`、`close` 与 nil Channel + +`select` 允许一个 goroutine 同时等待多条通信边: + +```go +select { +case job := <-jobs: + return handle(job) +case <-ctx.Done(): + return ctx.Err() +} +``` + +它是一种调度接口:执行单元声明自己依赖哪些事件,runtime 在其中一项 ready 时恢复它。 + +`close(ch)` 发布的是“不会再有新值”的生命周期状态。关闭后,接收者先读完 buffer,再通过 `value, ok := <-ch` 观察结束。关闭还可以充当广播,因为所有等待者都能观察到它。 + +nil Channel 则永远不会 ready。在 `select` 中把某个 Channel 变量设为 nil,可以动态禁用一条分支,构造小型并发状态机。 + +### Pipeline 必须拥有取消路径 + +多个 stage 可以由 Channel 连接成 pipeline,也可以通过多个 goroutine 形成 fan-out 和 fan-in。但如果下游提前退出,上游可能永久阻塞在 send 上并泄漏 goroutine。 + +```go +select { +case out <- result: +case <-ctx.Done(): + return +} +``` + +每个可能长期阻塞的 send 或 receive,都应该回答:如果另一端永远不会再出现,这个 goroutine 怎样退出? + +Go Channel 的真正特色,是没有把数据流与控制流彻底分开。一次通信既传递 value,也表达 dependency、synchronization 和 backpressure: + +```text +communication = dependency + synchronization + backpressure +``` + +这个模型也启发了另一派 subagent 系统:如果每个 Agent 都拥有一只 inbox,消息到达本身是否可以成为调度条件? + +## Codex Subagent:用 Mailbox 组织协作 + +Codex 给出的答案是肯定的。它保留 parent-child delegation,同时把每个 Agent 建模成有身份、有独立历史、可以持续接收消息的执行单元。 + +同一棵 subagent tree 中的 Agent 拥有可寻址路径: + +```text +/root +├── /root/runtime_review +├── /root/storage_review +│ └── /root/storage_review/query_analysis +└── /root/test_runner +``` + +这套模型与 Actor system 很接近: + +```text +Actor identity = AgentPath +Actor state = Thread history +Actor mailbox = Session InputQueue +Actor activation = Turn +``` + +### Mailbox 是 Session 的私有队列 + +Codex Core 的 `InputQueue` 把 payload 与 wakeup 分开: + +```rust +struct InputQueue { + activity_tx: watch::Sender, + mailbox_pending_mails: Mutex>, +} +``` + +`VecDeque` 保存 FIFO 消息,Tokio `watch` Channel 通知等待者 mailbox 发生变化。通知可以合并,因为 queue 才是消息事实来源。 + +这不是多个 worker 竞争 claim 的共享 inbox。每个 Session 都有自己的 mailbox,每封 `InterAgentCommunication` 在投递前就已经指定 `author`、`recipient`、`content` 和 `trigger_turn`。 + +### Message 也携带调度意图 + +Codex V2 区分两种投递: + +```text +send_message = QueueOnly +followup_task = TriggerTurn +``` + +`send_message` 只把消息放进目标 inbox。目标 Agent 正在运行时,它会在后续模型边界看到消息;目标 Agent 已经空闲时,消息等待下一次自然 activation。 + +`followup_task` 会设置 `trigger_turn=true`。如果目标 Agent 已经空闲,pending-work scheduler 可以为它创建新的 Turn。 + +```text + InterAgentCommunication + │ + ┌──────────┴──────────┐ + │ │ + trigger_turn = false trigger_turn = true + │ │ + queue message wake idle Agent +``` + +所以一封消息同时表达 information、recipient 和 scheduling intent。 + +### Agent 在模型边界收信 + +消息不会修改一次已经发出的 LLM sampling request。它先进入 mailbox,等待 Turn loop 再次构造模型 context: + +```text +Agent B starts sampling + │ +Agent A sends a message + │ + ▼ + B.mailbox.enqueue + │ + current sampling ends + │ + ▼ + drain mailbox + │ + ▼ +build next model request +``` + +Codex 还维护 `MailboxDeliveryPhase`。Turn 开始时,邮件可以加入当前执行;一旦 runtime 已经记录用户可见的 final answer,迟到消息就被留给下一轮,避免已经结束的答案被后台消息悄悄续写。 + +### Completion 本身也是一封消息 + +Codex 为 child 启动 completion watcher。child 进入终态后,watcher 构造一封从 child 发往 parent 的 `InterAgentCommunication`,并把它放进 parent mailbox。 + +这封 completion message 的 `trigger_turn` 是 false。结果首先是一条进入 parent inbox 的事实,而不是强制 parent 立即推理的中断。 + +`wait_agent` 因此也不直接从指定 child 拉取正文。它订阅当前 Session 的 mailbox activity: + +```text +wait_agent + │ + ├── new mail ─────> wake + ├── user steer ───> interrupt wait + └── deadline ─────> timeout +``` + +工具只负责 suspend 和 wakeup;消息正文仍保存在 mailbox 中,随后由 Turn loop 放进模型 context。 + +### Workflow 在 Conversation 中展开 + +DAG 系统把依赖写成显式边,Codex 的协作则可能表现为一串动态消息: + +```text +Root ──task──────> Agent A +Root ──task──────> Agent B +Agent A ──note───> Agent B +Agent B ──result─> Root +Root ──follow-up─> Agent A +Agent A ──result─> Root +``` + +Agent A 可以把新发现立即告诉 Agent B,主 Agent 也可以在 child 尚未完成时补充约束。实际 workflow 不必预先完整存在,而是在 conversation 中逐步生长。 + +灵活性也带来代价。控制流分散在消息历史中;想解释 Agent 为什么改变方向,需要回放它收到的 mailbox;想知道一项工作何时真正 ready,也不能只数一张全局 DAG 的入边。 + +Codex 因而可以概括为:以主 Agent 委派为入口,以 Actor mailbox 作为协作平面。 + +## 总结:Workflow 与 Collaboration + +Maka 和 Codex 都能创建多个 subagent,也都支持并行和 follow-up,但它们选择了不同的系统原语。 + +| 维度 | Maka workflow | Codex mailbox | +| -------- | ------------------------------ | -------------------------------------- | +| 核心抽象 | DAG 中的 operator 与 edge | 可寻址 Agent 与私有 inbox | +| 任务产生 | 主 Agent 写 schedule | parent spawn 或 Agent 发送 follow-up | +| 调度条件 | Coordinator 计算节点 readiness | 消息到达、`trigger_turn` 与 Agent 状态 | +| 数据传递 | record 沿依赖边成为下游输入 | message 进入目标 Agent context | +| 横向交流 | child 之间不需要通信 | Agent 可以向其他 Agent 发消息 | +| 状态观察 | 读取全局 Graph snapshot | 检查各 Agent 状态并消费 inbox | +| 主要优势 | 显式、可审计、容易确定性恢复 | 灵活、可动态协商、适合未知路径 | +| 主要代价 | 临时协商必须回到 Graph | 控制流隐含,消息与 context 容易膨胀 | + +Workflow 适合依赖明确、结果可结构化、执行时间较长并且必须可靠恢复的任务。代码扫描、批量测试、数据处理和多阶段 research synthesis 都可以被建模为 operator 与 record。 + +Mailbox 适合下一步取决于语义发现、角色需要不断交换信息、计划无法预先穷举的任务。设计讨论、交叉审查和开放式调查更接近这种协作。 + +两者真正的分界并不是“是否使用 subagent”,而是把协调状态放在哪里: + +```text +Maka: coordination lives in the Graph +Codex: coordination lives in the Conversation +``` + +Graph 把计划从模型上下文中提取出来,交给确定性系统推进;Conversation 保留 Agent 的交流自由,让计划在运行中自然形成。前者更像数据库执行引擎,后者更像 Actor system。 + +这也解释了为什么 Maka 刻意不让 subagent 互相聊天。它并不是认为 Agent 无法协作,而是选择把协作编译成显式的 schedule:模型负责语义判断,Runtime 负责执行事实,Coordinator 负责推进依赖。 + +Multi-agent 调度最终不是“启动多少模型”的问题,而是一个更传统的系统问题:如何表示状态,如何传递依赖,如何控制并发,以及在任何一个执行者退出以后,系统还能否知道下一步该做什么。 + +## 延伸阅读 + +- [Linux `fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html) +- [Apache DataFusion:Reading Explain Plans](https://datafusion.apache.org/user-guide/explain-usage.html) +- [Apache Arrow:Acero Overview](https://arrow.apache.org/docs/cpp/acero/overview.html) +- [The Go Programming Language Specification: Channel types](https://go.dev/ref/spec#Channel_types) +- [The Go Memory Model](https://go.dev/ref/mem) +- [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines) +- [Codex `InputQueue` and mailbox](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/session/input_queue.rs#L66-L186) +- [Codex MultiAgent V2 message delivery](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs#L12-L127) +- [Codex mailbox-driven Turn scheduling](https://github.com/openai/codex/blob/8e6a44b428e31f91b21edc97904fcdf4f0931ade/codex-rs/core/src/tasks/mod.rs#L422-L508) From e81bc3cfa4c9db3bfeabd47dec75e70d1016b537 Mon Sep 17 00:00:00 2001 From: likun Date: Fri, 4 Sep 2026 15:46:33 +0800 Subject: [PATCH 2/2] docs(blog): apply editorial review corrections Adopt the reviewer-provided bilingual polish, then correct implementation boundaries around CoW, Maka supervision, Go channels, and Codex mailboxes. Generated-by: Codex --- docs/blogs/multi-agent-scheduling.md | 290 ++++++++++----------- docs/blogs/multi-agent-scheduling.zh-CN.md | 280 ++++++++++---------- 2 files changed, 285 insertions(+), 285 deletions(-) diff --git a/docs/blogs/multi-agent-scheduling.md b/docs/blogs/multi-agent-scheduling.md index 0974affc2a..a22303f22a 100644 --- a/docs/blogs/multi-agent-scheduling.md +++ b/docs/blogs/multi-agent-scheduling.md @@ -21,36 +21,36 @@ # From Copy-on-Write to Mailboxes: Two Paths for Multi-Agent Scheduling -When an agent starts creating subagents, the most intuitive explanation is that it has launched more models to work in parallel. The difficult part, however, is not parallelism. It is scheduling: which context a subagent inherits, who decomposes the work, how dependencies are represented, how results are delivered, how execution recovers after failure, and whether active agents should communicate with one another. +In multi-agent architectures, creating a subagent is often simplified as running multiple model instances in parallel. The true engineering bottleneck lies in the scheduling topology: what context a subagent inherits, who decomposes and dispatches tasks, how dependencies are expressed, how results are reliably handed off, how systems recover deterministically from failures, and whether concurrent agents require direct communication channels. -These questions lead multi-agent systems down two distinct paths. +These trade-offs divide multi-agent systems into two distinct evolutionary paths. -One path treats a subagent as an operator. The main agent writes a workflow, a scheduler advances it according to explicit dependencies, and results travel downstream along directed edges. The other path treats a subagent as a participant. Every agent has an identity and a mailbox, agents coordinate by sending messages, and the actual workflow unfolds through conversation. +The first path treats subagents as workflow operators: the main agent defines an execution graph, the scheduler advances steps based on explicit dependencies, and data artifacts flow unidirectionally along directed edges. The second path treats subagents as collaborative participants: each agent possesses an addressable identity and a private mailbox, coordinating via asynchronous message passing while the effective control flow unfolds across multi-turn conversations. -Maka takes the first path. The newer Codex subagent design clearly belongs to the second. To understand both, we can begin with the way an operating system creates an execution branch cheaply. +Maka implements the explicit workflow graph approach, whereas the Codex subagent architecture represents the message-driven pattern. Understanding these designs begins with examining how operating systems manage state branching efficiently during process creation. -## Copy-on-Write: Share First, Diverge on Mutation +## Copy-on-Write: Branching State on Mutation -Strictly speaking, copy-on-write is not a threading model. Linux threads normally share one virtual address space. The classic use of copy-on-write appears when `fork()` creates a process. +In operating system design, Copy-on-Write (CoW) optimizes the cost of branching execution state. Linux threads share a single virtual address space by default, while classic CoW governs process creation during `fork()` system calls. -If `fork()` copied all of the parent's physical memory immediately, the cost of creating a child would grow linearly with the parent's memory footprint. Worse, a child often calls `exec()` shortly afterward, so most of the copied pages would never be read. +If `fork()` performed a deep physical memory copy immediately, child process instantiation costs would scale linearly with parent memory footprint. Child processes typically call `exec()` shortly after creation, discarding freshly duplicated memory pages before reading them. -Linux therefore copies the logical view before copying all physical data. After `fork()`, the parent and child have logically independent virtual address spaces, but their page tables may initially refer to the same read-only physical pages: +Linux optimizes this by creating separate page tables whose entries initially map the same underlying physical frames. Upon `fork()`, the kernel assigns an independent virtual address space to the child, points corresponding page table entries at shared physical pages, and marks the private writable mappings as read-only: ```text -Parent virtual pages ─┐ - ├──> shared physical pages -Child virtual pages ──┘ +Parent virtual pages ──┐ + ├──> shared physical pages +Child virtual pages ───┘ ``` -The pages remain shared while both processes only read them. When either process first writes to a page, the CPU raises a page fault, the kernel copies that page, and the writer mutates its private copy: +Both processes share physical pages during read-only access. When either process issues a write, the memory management unit (MMU) triggers a page fault. The kernel intercepts the fault, allocates a fresh physical frame, and grants write permissions to the mutating process: ```text Before write -Parent ─┐ - ├──> Page A -Child ──┘ +Parent ──┐ + ├──> Page A +Child ───┘ After child writes @@ -58,9 +58,9 @@ Parent ─────> Page A Child ─────> Page A' ``` -The essential property of copy-on-write is not merely faster copying. It defers copying until divergence actually occurs. Creating a branch requires a new identity and a sharing relationship; its cost follows the amount of changed state rather than the size of the complete state. +Copy-on-Write defers data duplication to the precise point of divergence. Branch creation incurs minimal metadata overhead, with total cost governed by actual mutation volume rather than total state magnitude. -The idea transfers naturally to agent systems. A subagent can fork from a prefix of the main agent's history, initially sharing existing context and then recording only its own incremental events: +This architectural principle maps directly to agent systems. A subagent can fork from the parent agent conversation history, sharing the initial context prefix while recording subsequent events to a private delta stream: ```text Shared conversation prefix @@ -70,59 +70,59 @@ Shared conversation prefix Main delta Child delta ``` -Context, however, is not an ordinary memory page. A parent agent's history mixes user intent, temporary reasoning, tool logs, permission decisions, and abandoned hypotheses. Full inheritance is convenient, but it also copies noise, stale assumptions, and token cost into the child. +However, model context differs fundamentally from physical memory pages. Parent conversation history may contain raw user prompts, intermediate assistant turns, tool diagnostic output, authorization events, and discarded trial paths. Unrestricted context inheritance passes historical noise, stale assumptions, and compounding token costs directly to child tasks. -The first multi-agent design choice is therefore not just how to copy cheaply. It is how much to copy at all. +Multi-agent architectures must therefore define the exact boundary and granularity of context propagation. -## A Subagent Is a Tool, Not a Coworker +## Subagents as Task-Scoped Tools -Maka gives a strong answer: a subagent does not automatically inherit the parent agent's conversation history. +Maka enforces a strict isolation boundary: subagents never automatically inherit the full conversation history of the parent agent. -When the main agent calls `agent_spawn`, it must provide a bounded, self-contained task: +When the main agent invokes `agent_spawn`, it must supply a self-contained task specification: ```text agent_spawn({ subagent_id: "local-reader", - task: "Inspect concurrent writes in the storage module and cite files and symbols" + task: "Inspect how the storage package handles concurrent writes, citing files and symbols" }) ``` -The runtime creates an independent child Session and injects its role, tools, permissions, and workspace boundary. The child's first model invocation starts from its own history. It sees the task explicitly supplied by the main agent rather than the entire parent conversation. +The runtime provisions an independent child session with its own conversation history, role instructions, bounded tool registry, permission scope, and workspace boundary. The child does not receive the parent's full conversation; its initial context combines runtime instructions with the explicit task specification, keeping unrelated parent history outside the child working set. -The main agent must compile implicit context into an independently executable specification: +This contract requires the main agent to compile implicit conversational context into a standalone specification: ```text -Inspect concurrent writes under packages/storage. +Investigate concurrent write handling in packages/storage. -Answer: -1. Which objects provide concurrency control? -2. How are conflicts detected? -3. Cite the relevant files and symbols. -4. Perform read-only research; do not edit code. +Provide answers for: +1. Which objects manage concurrency control; +2. How conflicts are detected; +3. Specific files and code symbols; +4. Perform read-only inspection without modifying files. ``` -Do the main agent and subagent need to communicate? Maka's answer is that they do not need an ongoing conversation. +Maka eliminates ongoing conversational chatter between the main agent and its subagents: ```text Main Agent ── task ──> Subagent -Main Agent <─ result ─ Subagent +Main Agent <── result ── Subagent ``` -There is no mailbox between them and no protocol for negotiating the next step halfway through execution. The main agent decomposes the problem, selects the executor, and synthesizes results. The subagent completes the bounded task. Runtime events may be projected into the UI for the user to observe, but that presentation is not an inter-agent conversation. +No shared mailbox or intermediate negotiation protocol exists between the two tiers. The main agent decomposes high-level objectives, selects operators, and synthesizes final outcomes; subagents focus on localized task execution. Runtime execution events can be projected to the user interface for observability, serving as one-way telemetry rather than inter-agent dialogue. -From the caller's perspective, a subagent still honors a Tool contract: accept a task, execute a constrained process, and return a status, summary, and artifact references. +From the caller perspective, a subagent adheres to standard tool semantics: it consumes structured input parameters, executes within assigned runtime boundaries, and returns terminal execution state, summary metrics, and artifact references: ```text result = subagent(role, tools, task, workspace) ``` -That creates a clean context boundary, but raises another question. If tasks have complex dependencies and children do not coordinate through conversation, how does the system represent the global plan? +While this contract ensures scoped execution boundaries, it raises an architectural challenge: when subtasks possess complex causal dependencies without direct communication channels, the system requires a formal mechanism to represent and advance the global execution plan. -## DAGs: How Databases Turn Intent into Execution +## DAGs: The Relational Engine Execution Model -When a computation consists of interdependent steps, a Directed Acyclic Graph is often a more natural representation than a list. +Directed Acyclic Graphs (DAGs) provide a rigorous structural representation for multi-stage computation involving dependency constraints. -A list imposes a total order: A, then B, then C. A DAG represents a partial order. Edges declare only required precedence, so unrelated nodes may run concurrently. +Flat sequential lists enforce total ordering (A, then B, then C). A DAG defines a partial order: directed edges enforce mandatory precedence constraints, while disconnected nodes execute with natural concurrency: ```text A ───────> C @@ -130,15 +130,15 @@ A ───────> C B ───────> D ``` -A must precede C and B must precede D, but A and B have no inherent ordering. A scheduler does not need a complete execution sequence. It only needs to find nodes whose input conditions are satisfied. +Here, A must precede C, and B must precede D, while A and B share no temporal constraints. The scheduler avoids deriving an arbitrary global sequence, focusing exclusively on nodes whose incoming dependencies have been satisfied: ```text -Node = a unit of computation -Edge = a dependency or data flow -Ready = the node's input conditions are satisfied +Node = Computational unit (Operator) +Edge = Dependency constraint or data flow +Ready = All inbound preconditions satisfied ``` -Databases have long separated what should be computed from how it should execute. A SQL statement first becomes a logical plan: +Relational database systems long ago decoupled declarative intent from physical execution. When a user submits a SQL statement, the query engine compiles an abstract Logical Plan: ```text Aggregate by region @@ -150,9 +150,9 @@ Databases have long separated what should be computed from how it should execute Scan orders Scan customers ``` -The logical plan describes relational semantics. An optimizer can push down filters, prune columns, reorder joins, and simplify expressions as long as the result remains unchanged. +The logical plan models relational algebraic semantics. The query optimizer applies rule-based transformations such as predicate pushdown, column pruning, join reordering, and expression simplification while preserving output equivalence. -A physical planner then lowers abstract operators into concrete implementations: +The Physical Planner subsequently translates logical operators into concrete engine implementations: ```text FinalHashAggregateExec @@ -168,19 +168,19 @@ A physical planner then lowers abstract operators into concrete implementations: ParquetScanExec ParquetScanExec ``` -The physical plan chooses join algorithms, partition counts, parallelism, and data exchanges. The same logical plan may produce different physical plans as data volume, partitioning, available memory, and CPU resources change. +The physical plan selects join algorithms, partition layouts, target parallelism, and exchange operators. The same logical plan compiles into distinct physical plans based on dataset cardinality, cluster distribution, and memory allocations. -Yet a physical plan is still not execution. The runtime must instantiate state, allocate resources, move data, and handle completion, cancellation, errors, and backpressure. +Once the physical plan compiles, the execution engine instantiates pipelines, manages memory pools, routes record batches, and handles completion, cancellation, errors, and backpressure signals. -Operators that can immediately consume and produce batches form a pipeline: +Operators that consume and produce record batches in a streaming manner combine into execution pipelines: ```text Scan ──batch──> Filter ──batch──> Project ──batch──> Sink ``` -A sort, the build side of a hash join, or a global aggregate may need to accumulate input before producing output and therefore becomes a pipeline breaker. At this layer, the execution engine finally decides which pipelines are ready and how upstream and downstream work advance concurrently. +In contrast, sort operations, the build side of hash joins, or global aggregations must buffer full input streams before emitting records, forming pipeline breakers. The engine schedules pipeline execution dynamically based on readiness and available resource capacity. -Apache Arrow Acero provides a compact example. A `Declaration` describes a node to construct, `ExecPlan` and `ExecNode` represent the physical graph for one execution, and `ExecBatch` is the data moving along its edges. +Apache Arrow Acero provides a clean reference implementation: `Declaration` defines node configurations, `ExecPlan` and `ExecNode` represent physical execution topologies, and `ExecBatch` serves as the standardized unit of data passing along edges. ```text SQL @@ -198,23 +198,25 @@ Physical Plan Running Pipelines ``` -The central database lesson is that a DAG is not execution itself. It is an intermediate representation that a system can optimize, lower, instantiate, and eventually schedule. +Relational engines demonstrate that a DAG functions primarily as an Intermediate Representation (IR), enabling systematic optimization, lowering, and deterministic scheduling. -## Maka Agent Graph: Agents Write Plans, Systems Advance Them +## Maka Agent Graph: Declarative Planning and Engine Progression -A database can usually construct a reasonably complete physical plan before execution. An agent rarely knows its whole plan in advance. +Relational query engines produce deterministic physical plans prior to execution, whereas agent plans evolve dynamically based on runtime discovery. -An investigation may reveal a new problem. An implementation result may change the validation strategy. When a node fails, the main agent may choose a different path instead of retrying mechanically. A Maka Agent Graph is therefore a DAG that grows while it runs. +Exploratory analysis uncovers unindexed code paths, intermediate builds shift validation requirements, and task failures require path recalculation rather than blind retries. Maka models the Agent Graph as a dynamically expanding DAG throughout the session lifecycle. -Maka divides the work among three responsibilities: +The architecture enforces a strict separation of operational concerns: -> The main agent writes the plan, the Coordinator advances it, and the Supervisor observes it. +- **Main Agent:** Owns high-level goal decomposition and semantic decisions. +- **Coordinator:** Drives dependency resolution and topological convergence. +- **Supervisor:** Observes graph checkpoints and resumes semantic judgment when the workflow requires it. ### The Main Agent Writes Durable Intent -Only the main agent in the root Session owns Graph control tools. It can append work, stop or replace existing work, select final results, and close the Graph. Child Sessions cannot mutate the global topology in return. +Only the main agent within the root session holds graph mutation capabilities. It registers operator definitions, schedules dependent work, deprecates historical branches, and selects final artifacts to close the graph; child sessions cannot mutate global topology. -The main agent submits schedule revisions through `update_agent_graph`. Work without input dependencies can run in parallel; later work refers to committed upstream result records: +The main agent records plan modifications via `update_agent_graph`. Independent nodes run concurrently, while dependent nodes explicitly reference committed upstream artifacts: ```text Runtime review result ─┐ @@ -222,13 +224,13 @@ Runtime review result ─┐ Storage review result ─┘ ``` -This is not an ephemeral instruction to start three processes now. It is durable intent: which work to add, what its input frontier is, which work should stop or be replaced, and which results are ultimately selected. +This interaction commits Durable Intent: specifying newly provisioned work units, current input frontiers, targeted node deprecations, and terminal outcome selections. -Schedule updates are committed to SQLite as append-only revisions with their source Session, Run, Turn, and Tool Call identity. If the main agent exits, the plan does not disappear with its model context. +Schedule mutations append to SQLite as immutable revision records marked with session, run, turn, and call identifiers. Even if the host process terminates unexpectedly, committed execution plans remain fully preserved in durable storage. -### The Coordinator Is a Reconciler +### The Coordinator Acts as a State Reconciler -The Coordinator does not keep one authoritative mutable DAG in memory. Every reconciliation reads durable state again: +The Coordinator avoids retaining authoritative in-memory graph objects. Every reconciliation cycle reconstructs baseline truth directly from durable storage: ```text SQLite control plane @@ -242,7 +244,7 @@ SQLite control plane Coordinator reconstructs a snapshot ``` -It folds revisions into the current plan, assembles provisions into a topology, and combines that view with AgentRuns and committed RuntimeEvents. From those facts it calculates which work has completed, which inputs are missing, and which nodes are ready. +The Coordinator folds committed revision logs into the current plan snapshot, constructs the operational topology from registered operators, and correlates completed `RuntimeEvents` to evaluate node readiness: ```text Observe durable state @@ -263,11 +265,11 @@ Claim exact Turn / Run identities Dispatch child AgentRuns ``` -Maka currently uses an event-driven, single-flight driver rather than a fixed `setInterval` database scan. A new schedule, a child RuntimeEvent, or host recovery can request reconciliation. Only one driver advances a Graph at a time, and repeated wakes coalesce into another pass. +Maka uses an event-driven single-flight driver rather than a fixed-interval database polling loop. New schedule commits, child completion events, or host recovery routines trigger reconciliation; a single driver instance runs per graph, coalescing concurrent wakeups into subsequent iterations. -### SQLite Is the Control Plane +### SQLite Serves as the Control Plane -The Graph does not introduce a second agent runtime. SQLite stores scheduling facts, while model invocations, Tool Calls, permissions, stopping, and RuntimeEvent persistence remain the responsibility of the Session Runtime. +The Agent Graph layer avoids duplicating core agent runtime capabilities. SQLite acts purely as the control plane for scheduling facts, while model sampling, tool execution, permission arbitration, cancellation, and event logging remain delegated to the established Session Runtime. ```text Main Agent ──> SQLite schedule @@ -282,13 +284,13 @@ Main Agent ──> SQLite schedule committed RuntimeEvents ``` -A child Session is a stable operator container, an AgentRun is one activation, and only a committed RuntimeEvent can become a record consumed by the Graph. +Child sessions function as stable operator containers, while `AgentRun` represents an individual execution lifecycle. Downstream graph nodes consume committed result records backed by runtime events, rather than treating every `RuntimeEvent` as graph input. -### Claims Separate Ready from Execute +### Claims Decouple Readiness from Execution Admission -Because the Coordinator can rebuild its snapshot repeatedly, it can calculate the same node as ready more than once. Starting a model whenever readiness is observed would allow a crash or retry to duplicate execution. +Because the Coordinator reconstructs plan snapshots on every cycle, identical nodes can evaluate as ready across multiple reconciliation passes. Triggering model calls immediately upon detecting readiness risks duplicate executions during retries or transient scheduling jitter. -Before execution, Maka writes a conditional claim to SQLite and binds a deterministic intent to a specific operator, Session, Turn, and Run identity: +Maka requires writing a conditional claim to SQLite before execution begins, binding ready work to an allocated operator, session, turn, and run identity: ```text ready intent @@ -300,13 +302,13 @@ conditional claim └── new claim ───────> execute the allocated Run ``` -Readiness remains a recomputable projection. Execution admission becomes a durable fact. +Readiness evaluation remains a reproducible, side-effect-free projection, whereas execution admission commits as an atomic, durable fact. -### The Supervisor Regains Judgment at Checkpoints +### The Supervisor Restores Semantic Judgment at Checkpoints -The Coordinator can advance an existing plan deterministically, but it should not decide whether two investigations contradict each other, or whether a failed node calls for a retry, replacement, or change in direction. Those semantic decisions remain with the main agent. +Deterministic Coordinators excel at dependency management, yet cannot replace language models for high-level semantic arbitration: resolving contradictions between reports, deciding whether to retry or replace failing components, or altering strategic direction. Strategic judgment remains reserved for the main agent. -After writing one round of the schedule, the main agent can end its current supervisor turn. The Coordinator advances the Graph asynchronously. At a durable checkpoint, the Host creates another supervisor turn: +After writing a schedule update, the main agent concludes its current supervisor turn. The Coordinator drives the execution graph asynchronously; once the graph reaches a durable checkpoint, the host environment wakes the main agent into a fresh supervisor turn: ```text Main Agent schedules work @@ -321,19 +323,19 @@ durable checkpoint Host wakes Main Agent ``` -The main agent reads a bounded Graph snapshot and, when needed, a child's committed result. It can then add another round of work, stop or replace obsolete work, or select results and finish the Graph. +The main agent inspects bounded graph snapshots and committed child outputs, scheduling subsequent work units, pruning obsolete branches, or marking the graph complete. -The loop contains two kinds of intelligence. The main agent contributes semantic intelligence through decomposition, judgment, and synthesis. The Coordinator contributes systems intelligence through persistence, topology reconstruction, concurrent advancement, and failure recovery. +This lifecycle combines two complementary capabilities: the main agent provides semantic decomposition and synthesis, while the Coordinator enforces transaction durability, topological planning, concurrency control, and fault tolerance. -## Go Channels: Communication Is Scheduling +## Go Channels: Communication as Scheduling -A DAG describes dependency, but does not by itself implement waiting, wakeup, and backpressure. Go's concurrency model offers another way to think about scheduling. +DAGs excel at modeling macro-level dependencies, yet runtime scheduling requires low-level primitives for task suspension, wakeup notifications, and backpressure. The Go concurrency model provides a classic systems perspective on communication-driven coordination. -A goroutine is a lightweight execution unit scheduled by the Go runtime. The runtime model often summarized as G-M-P multiplexes many goroutines over fewer OS threads: G is a goroutine, M is an OS thread, and P is the runtime resource required to execute Go code. +A goroutine is a lightweight execution unit scheduled by the Go runtime. The G-M-P scheduler multiplexes thousands of application goroutines across a small pool of operating system threads: G represents the goroutine, M represents an OS thread, and P represents logical processor resources required to execute Go code. -Goroutines make concurrent tasks cheap. Channels define how those tasks cooperate. +Goroutines provide cost-effective concurrency, while channels establish structured communication contracts between them. -### An Unbuffered Channel Is a Rendezvous +### Unbuffered Channels as Rendezvous Points ```go handoff := make(chan Result) @@ -341,27 +343,27 @@ go func() { handoff <- result }() received := <-handoff ``` -The sender of an unbuffered Channel waits for a receiver, and the receiver waits for a sender. Communication completes only when both sides reach the handoff point. It transfers not only a `Result`, but also the synchronization fact that both parties met there. +An unbuffered channel requires both sender and receiver to be ready before data transfers. The sender blocks until a receiver arrives, and the receiver blocks until a value is available. The send and receive operations therefore form a synchronization point between the two goroutines. -The Go memory model defines happens-before relations for Channel operations. After receiving the value, the receiver can observe writes completed by the sender before the send. A Channel therefore combines: +The Go memory model establishes strict happens-before guarantees for channel operations. The receiver observing a transmitted value is guaranteed to observe all memory writes performed by the sender prior to the send operation. A single channel transmission combines multiple coordination primitives: ```text value transfer + scheduling point + memory ordering ``` -### A Buffer Defines How Far a Producer May Lead +### Buffers Regulate Decoupling Capacity ```go jobs := make(chan Job, 32) ``` -A buffered Channel decouples a producer and consumer across a bounded distance. A send proceeds while capacity remains. When the buffer fills, the producer blocks and pressure propagates backward through the pipeline. +A buffered channel allows producers and consumers to decouple within a bounded capacity. As long as slots remain available, send operations complete without blocking; once the buffer fills, the producer suspends, propagating backpressure upstream. -The capacity `32` is not merely a performance setting. It defines how many units of work the producer may get ahead of the consumer. Too little capacity can suppress useful parallelism. Too much can accumulate obsolete work, consume memory, and delay the discovery of a slow downstream stage. +Buffer capacity determines the maximum lead a producer may hold over a consumer. Tight limits restrict throughput smoothing, while excessive buffers accumulate obsolete work, elevate memory pressure, and conceal downstream degradation. -### `select`, `close`, and nil Channels +### Multiplexing and Lifecycle Signaling -`select` lets one goroutine wait on several communication edges: +Go provides the `select` statement to allow a goroutine to monitor multiple channel events simultaneously: ```go select { @@ -372,15 +374,15 @@ case <-ctx.Done(): } ``` -It acts as a scheduling interface. An execution unit declares the events it depends on, and the runtime resumes it when one becomes ready. +The `select` construct functions as a declarative scheduling interface: the worker declares event dependencies, and the runtime awakens it when any condition resolves. -`close(ch)` publishes a lifecycle transition: no new values will arrive. Receivers first drain the buffer and then observe termination through `value, ok := <-ch`. Closing can also broadcast a signal because all waiting receivers can observe it. +The `close(ch)` primitive broadcasts lifecycle termination across all readers. Upon closure, receivers drain remaining buffered items, after which `value, ok := <-ch` signals that the channel has terminated. All receivers blocked on an empty closed channel can observe this terminal state. -A nil Channel can never become ready. Assigning nil to a Channel variable in a `select` dynamically disables that branch and makes it possible to build small concurrent state machines. +A nil channel never resolves. Dynamically setting a channel reference to nil inside a `select` block cleanly disables a specific branch without altering the outer loop structure, forming a compact state machine. -### Every Pipeline Needs Cancellation +### Cancellation Propagation in Pipelines -Channels naturally connect stages into pipelines and support fan-out and fan-in with multiple goroutines. But when a downstream stage exits early, an upstream producer may remain blocked forever on a send and leak its goroutine. +Multiple execution stages connect via channels to form processing pipelines, expanding into fan-out and fan-in topologies. However, if downstream stages exit prematurely, uncoordinated upstream producers block indefinitely on send operations, leaking goroutines. ```go select { @@ -390,21 +392,21 @@ case <-ctx.Done(): } ``` -Every send or receive that may block indefinitely must answer one question: how does this goroutine exit if the other endpoint never appears again? +Any blocking communication site that may outlive its downstream consumer needs an explicit cancellation path so workers can terminate cleanly and release resources. -The distinctive property of a Go Channel is that it does not fully separate data flow from control flow. One communication carries a value while expressing dependency, synchronization, and backpressure: +Go channels integrate data passing with scheduling semantics: a single communication primitive handles data transport, dependency signaling, synchronization, and backpressure: ```text communication = dependency + synchronization + backpressure ``` -That model suggests another approach to subagents. If every agent owns an inbox, can message arrival itself become a scheduling condition? +This model inspires an alternative multi-agent coordination pattern: provisioning private inboxes for individual agents, allowing message delivery to act as the primary scheduling mechanism. -## Codex Subagents: Collaboration Through Mailboxes +## Codex Subagents: Mailbox-Driven Collaboration -Codex answers yes. It preserves parent-child delegation while modeling every agent as an execution unit with an identity, independent history, and an inbox that can receive messages over time. +Codex applies message-driven coordination to its subagent architecture. While preserving parent-child task delegation, it models each agent as an actor addressable within a root task tree, with dedicated conversation history and asynchronous communication through private mailboxes. -Agents in the same subagent tree have addressable paths: +Agents within the same root task tree share a hierarchical addressing namespace: ```text /root @@ -414,7 +416,7 @@ Agents in the same subagent tree have addressable paths: └── /root/test_runner ``` -The design resembles an Actor system: +This architecture closely mirrors the classic Actor model: ```text Actor identity = AgentPath @@ -423,9 +425,9 @@ Actor mailbox = Session InputQueue Actor activation = Turn ``` -### A Mailbox Is Private to a Session +### Private Mailbox Queues per Session -Codex Core separates the payload queue from the wakeup signal in `InputQueue`: +The Codex Core `InputQueue` decouples payload storage from wakeup notifications: ```rust struct InputQueue { @@ -434,22 +436,22 @@ struct InputQueue { } ``` -The `VecDeque` stores FIFO messages. A Tokio `watch` Channel tells waiters that mailbox activity occurred. Notifications may coalesce because the queue, not the signal, is the source of message truth. +An in-memory `VecDeque` preserves FIFO message ordering while the session is resident, while a Tokio `watch` channel transmits change notifications to waiting schedulers. Wakeup signals may coalesce safely because pending payloads remain in the queue; this queue does not itself imply durable persistence. -This is not a shared inbox from which workers compete to claim work. Every Session has a private mailbox. Before delivery, every `InterAgentCommunication` already identifies its `author`, `recipient`, `content`, and `trigger_turn` behavior. +This design avoids competitive worker claim patterns. Each session owns a dedicated mailbox, and every `InterAgentCommunication` payload specifies author, recipient, content, and a turn trigger flag (`trigger_turn`) prior to dispatch. -### A Message Also Carries Scheduling Intent +### Messages Convey Scheduling Intent -Codex V2 distinguishes two delivery modes: +Codex V2 differentiates message delivery into two scheduling tiers: ```text send_message = QueueOnly followup_task = TriggerTurn ``` -`send_message` places content in the target inbox. A running agent sees it at a later model boundary. If the target is idle, the message waits for its next natural activation. +The `send_message` operation enqueues the payload without forcing an immediate wakeup. Active recipients inspect incoming messages at their next reasoning boundary; idle recipients hold messages until subsequent conversational turns activate them. -`followup_task` sets `trigger_turn=true`. If the target is idle, the pending-work scheduler may create a new Turn for it. +The `followup_task` operation marks `trigger_turn = true`. If the recipient is idle, the task scheduler immediately provisions a new turn to process the payload. ```text InterAgentCommunication @@ -461,11 +463,11 @@ followup_task = TriggerTurn queue message wake idle Agent ``` -A message therefore carries information, a recipient, and scheduling intent at the same time. +A message transmission simultaneously conveys conversational information, destination addressing, and execution scheduling intent. -### Agents Read Mail at Model Boundaries +### Controlled Message Ingestion at Model Boundaries -A message cannot alter an LLM sampling request that has already been sent. It first enters the mailbox and waits for the Turn loop to construct another model context: +External messages do not interrupt in-flight LLM sampling requests. New arrivals queue in the mailbox, merging into the conversation context when the active turn completes and constructs the next model request: ```text Agent B starts sampling @@ -484,15 +486,15 @@ Agent A sends a message build next model request ``` -Codex also tracks a `MailboxDeliveryPhase`. At the beginning of a Turn, new mail may join the current execution. Once the runtime has recorded user-visible final output, late mail is left for a later Turn so that background messages cannot silently extend an answer that already appeared complete. +Codex regulates ingestion via `MailboxDeliveryPhase`. Pending messages drain into context during early turn phases; once the runtime records a final user-visible response, late-arriving messages defer to subsequent turns, preventing external inputs from altering finalized outputs. -### Completion Is Also a Message +### Task Completion Delivered as Structured Messages -Codex starts a completion watcher for a child. When the child reaches a terminal status, the watcher constructs an `InterAgentCommunication` from the child to the parent and places it in the parent's mailbox. +Codex attaches a completion watcher to child sessions. When a child reaches terminal state, the watcher constructs an `InterAgentCommunication` payload from child to parent, depositing it into the parent mailbox. -That completion message uses `trigger_turn=false`. A result first becomes a fact in the parent's inbox rather than an interruption that always forces immediate reasoning. +The completion message sets `trigger_turn = false`. Results enter the parent inbox as factual events rather than disruptive interrupts, preserving parent reasoning continuity. -For the same reason, `wait_agent` does not pull the response body directly from a selected child. It subscribes to mailbox activity on the current Session: +The `wait_agent` tool waits on parent mailbox activity instead of returning a child result directly: ```text wait_agent @@ -502,11 +504,11 @@ wait_agent └── deadline ─────> timeout ``` -The tool handles suspension and wakeup. The message body remains in the mailbox and is subsequently added to model context by the Turn loop. +The tool coordinates suspension and wakeups, while message content remains buffered within the mailbox until the turn loop incorporates it into model context. -### The Workflow Unfolds in Conversation +### Conversational Workflow Evolution -A DAG system writes dependencies as explicit edges. The same collaboration in Codex may appear as a dynamic sequence of messages: +DAG systems compile execution dependencies into explicit topological edges, whereas mailbox architectures express workflows through dynamic message exchanges: ```text Root ──task──────> Agent A @@ -517,43 +519,41 @@ Root ──follow-up─> Agent A Agent A ──result─> Root ``` -Agent A can immediately tell Agent B about a discovery, and the main agent can add constraints before a child finishes. The complete workflow does not have to exist in advance. It grows through conversation. +Agent A shares findings with concurrent Agent B immediately, while the root agent injects steering constraints during child execution. Workflows evolve organically through conversation rather than requiring static upfront definition. -That flexibility has a cost. Control flow is distributed across message histories. Explaining why Agent B changed direction may require replaying its mailbox, and deciding when work is ready cannot be reduced to counting incoming edges in one global DAG. +This flexibility introduces architectural trade-offs: control flow distributes across message histories. Explaining strategic adjustments requires reconstructing full mailbox absorption traces, and evaluating task readiness cannot be determined from topological graph degrees alone. -Codex can therefore be summarized as main-agent delegation with an Actor mailbox as its collaboration plane. +The Codex architecture combines root-scoped parent-child delegation with actor-style private mailboxes to form a collaborative coordination layer. -## Conclusion: Workflow and Collaboration +## Architectural Trade-offs: Workflow Scheduling and Message Collaboration -Maka and Codex can both create subagents, execute work in parallel, and assign follow-ups, but they choose different systems primitives. +Maka and Codex support subagent delegation, concurrent execution, and iterative follow-ups, but diverge fundamentally in their underlying systems primitives. -| Dimension | Maka workflow | Codex mailbox | -| ------------------ | -------------------------------------------- | --------------------------------------------- | -| Core abstraction | Operators and edges in a DAG | Addressable agents with private inboxes | -| Work creation | The main agent writes a schedule | Parent spawn or an agent sends a follow-up | -| Scheduling signal | The Coordinator calculates node readiness | Message arrival, `trigger_turn`, agent status | -| Data transfer | Records become downstream edge inputs | Messages enter the target agent's context | -| Peer communication | Children do not need to communicate | Agents may message other agents | -| Observation | Read a global Graph snapshot | Inspect agent status and consume inboxes | -| Primary strength | Explicit, auditable, deterministic recovery | Flexible negotiation along unknown paths | -| Primary cost | Ad hoc coordination must return to the Graph | Implicit control flow and growing context | +| Dimension | Maka Workflow Architecture | Codex Mailbox Architecture | +| ------------------------ | ------------------------------------------- | ------------------------------------------------------------------ | +| **Core Abstraction** | Operators and edges within a DAG | Agents addressable within a root task tree, with private mailboxes | +| **Task Emission** | Main agent writes schedule revisions | Parent spawn or peer follow-up messages | +| **Scheduling Driver** | Coordinator evaluates node readiness | Message queuing, `trigger_turn`, and agent state | +| **Data Propagation** | Structured records pass along edges | Message payloads inject at turn boundaries | +| **Peer Communication** | No direct child-to-child channel is exposed | Supported within the root task tree via direct message passing | +| **Global Observability** | Reconstructible from graph snapshots | Aggregated across distributed mailboxes | +| **Primary Strength** | Explicit topology, deterministic recovery | Dynamic negotiation, exploratory adaptability | +| **Primary Trade-off** | Plan revisions require graph mutations | Implicit control flow, context expansion risks | -A workflow fits tasks with clear dependencies, structured outputs, long execution, and strong recovery requirements. Code scans, test matrices, data processing, and multi-stage research synthesis can all be modeled as operators and records. +The workflow architecture excels in scenarios featuring defined dependencies, structured artifacts, prolonged execution spans, and rigorous crash recovery requirements. Static code analysis, automated test suites, data pipelines, and multi-stage research synthesis map cleanly to operators and record streams. -A mailbox fits tasks whose next step depends on semantic discoveries, where roles must exchange findings, and where the plan cannot be enumerated in advance. Design discussions, cross-review, and open-ended investigations are closer to this kind of collaboration. +The mailbox architecture fits exploratory tasks where subsequent steps depend on semantic discovery, roles exchange continuous feedback, and execution paths resist upfront enumeration. Architectural deliberations, collaborative code reviews, and open-ended investigations align naturally with conversational messaging. -The real dividing line is not whether a system uses subagents. It is where coordination state lives: +The fundamental divergence lies in where coordination state resides: -```text -Maka: coordination lives in the Graph -Codex: coordination lives in the Conversation -``` +- **Maka:** Coordination lives in the Graph. +- **Codex:** Coordination lives in the Conversation. -A Graph extracts the plan from model context and gives a deterministic system responsibility for advancing it. A Conversation preserves freedom to communicate and lets the plan emerge during execution. The former resembles a database execution engine; the latter resembles an Actor system. +The Agent Graph extracts execution plans from model context, delegating progression to a deterministic system engine. The mailbox approach preserves conversational flexibility, allowing coordination to emerge dynamically. The former resembles relational database execution engines, while the latter reflects classic Actor concurrency systems. -This distinction also explains why Maka deliberately avoids conversations among subagents. It is not an assumption that agents cannot collaborate. It is a choice to compile collaboration into an explicit schedule: models provide semantic judgment, the Runtime owns execution facts, and the Coordinator advances dependencies. +This clarifies Maka design choice regarding subagent isolation: it compiles collaboration into transparent, auditable scheduling graphs. Models provide semantic reasoning, the runtime records execution ground truth, and the Coordinator advances data dependencies deterministically. -Multi-agent scheduling is ultimately not a question of how many models to start. It is a classic systems question: how to represent state, carry dependencies, control concurrency, and still know what to do next after any executor disappears. +Multi-agent scheduling ultimately addresses foundational distributed systems challenges: modeling state, passing dependencies reliably, bounding concurrency, and ensuring predictable forward progress when individual workers terminate. ## Further Reading diff --git a/docs/blogs/multi-agent-scheduling.zh-CN.md b/docs/blogs/multi-agent-scheduling.zh-CN.md index 660d1c8e10..66b2369e64 100644 --- a/docs/blogs/multi-agent-scheduling.zh-CN.md +++ b/docs/blogs/multi-agent-scheduling.zh-CN.md @@ -21,36 +21,36 @@ # 从 Copy-on-Write 到 Mailbox:Multi-Agent 调度的两条路径 -当一个 Agent 开始创建 subagent,最直觉的解释是“多开几个模型,并行完成任务”。但真正困难的问题并不在并行,而在调度:subagent 继承什么上下文,任务由谁拆解,依赖如何表达,结果怎样交付,失败以后如何恢复,以及执行中的 Agent 是否应该彼此交流。 +在 Multi-Agent 架构中,创建 Subagent 常被简化理解为“并行启动多个模型处理任务”。然而,系统设计的核心瓶颈在于调度拓扑:Subagent 应继承何种上下文、任务由谁拆解与分发、执行依赖如何严谨表达、产出结果如何可靠交付、单点故障后如何确定性恢复,以及并发 Agent 之间是否应当存在直接的通信通道。 -这些问题最终把 multi-agent 系统带向了两条不同的路线。 +这些工程权衡将 Multi-Agent 系统划分为两条截然不同的演进路径。 -一条路线把 subagent 当作 operator:主 Agent 编写 workflow,调度器根据显式依赖推进执行,结果沿有向边流向下游。另一条路线把 subagent 当作 participant:每个 Agent 都有身份和 mailbox,通过互发消息协调工作,实际流程在 conversation 中逐步展开。 +第一条路径将 Subagent 视为工作流算子(Operator):主 Agent 规划确定性执行图,调度内核依据显式依赖推进执行,数据与产物沿有向边单向流动。第二条路径将 Subagent 视为协作参与者(Participant):每个 Agent 具备独立身份与私有信箱(Mailbox),通过异步消息传递驱动协作,实际控制流在多轮对话中动态交织。 -Maka 选择了第一条路线,Codex 的新一代 subagent 协作则明显属于第二条。要理解这两种设计,我们先从操作系统如何廉价地创建执行分支说起。 +Maka 采用了第一条基于显式工作流图的路径,而 Codex 的 Subagent 体系则代表了第二条消息驱动的典型设计。理解这两类架构的前提,在于审视操作系统如何在进程派生中低成本管理状态分叉。 -## Copy-on-Write:先共享,写入时再分叉 +## Copy-on-Write:按需分叉的隔离哲学 -严格来说,Copy-on-Write 不是一种 thread 模型。Linux thread 通常共享同一份虚拟地址空间;经典的 Copy-on-Write 出现在 `fork()` 创建进程时。 +在操作系统设计中,Copy-on-Write(CoW,写时复制)是控制分支状态复制开销的核心机制。Linux 线程默认共享同一虚拟地址空间,而经典 CoW 则作用于 `fork()` 系统调用派生新进程的阶段。 -如果 `fork()` 立即复制父进程的全部物理内存,创建一个 child 的成本就会随父进程内存线性增长。更糟的是,child 往往很快调用 `exec()`,刚复制的大部分页面根本不会被读取。 +若 `fork()` 采取全量深拷贝策略,子进程创建开销将与父进程物理内存呈线性正比。更低效的是,子进程通常会迅速调用 `exec()` 载入新二进制映像,导致复制的大量内存页未被访问即遭丢弃。 -Linux 因此先复制逻辑视图,而不是所有物理数据。`fork()` 之后,父子进程拥有彼此独立的虚拟地址空间,但页表最初可以指向相同的物理页面,并把这些映射标记为只读: +Linux 通过建立彼此独立、但初始指向相同物理页帧的页表来延迟物理拷贝。`fork()` 触发时,内核为子进程建立独立的虚拟地址空间,让对应页表项映射同一组物理页,并将原本私有可写的映射标记为只读: ```text -Parent virtual pages ─┐ - ├──> shared physical pages -Child virtual pages ──┘ +Parent virtual pages ──┐ + ├──> shared physical pages +Child virtual pages ───┘ ``` -只要双方都在读取,这些页面就可以继续共享。某一方第一次写入时,CPU 触发 page fault,内核复制对应页面,并让写入方改写自己的副本: +在读操作占主导的阶段,两端完全复用同一份内存。一旦任一方首次发起写操作,内存管理单元(MMU)触发 Page Fault,内核捕获缺页中断并分配新的独立物理页,仅为执行写入的一方生成私有副本: ```text Before write -Parent ─┐ - ├──> Page A -Child ──┘ +Parent ──┐ + ├──> Page A +Child ───┘ After child writes @@ -58,9 +58,9 @@ Parent ─────> Page A Child ─────> Page A' ``` -Copy-on-Write 的关键并不是“复制更快”,而是把复制推迟到分歧真正发生的地方。创建分支只需要建立新的身份和共享关系,成本与实际修改量相关,而不是与完整状态大小相关。 +Copy-on-Write 的本质在于将数据复制的成本后推至物理分歧发生的时刻。状态分叉的初始代价仅包含创建轻量元数据与建立映射拓扑,总体开销由实际修改量界定,与全局状态规模解耦。 -这个思想很自然地被带进 Agent 系统。一个 subagent 可以从主 Agent 的历史前缀 fork,开始时共享已有 context,之后只记录自己的增量事件: +这一思想被直接引入 Agent 架构。Subagent 能够自父 Agent 的既有会话日志打分叉点,初始阶段引用父级上下文,后续仅追加自身的增量事件: ```text Shared conversation prefix @@ -70,15 +70,15 @@ Shared conversation prefix Main delta Child delta ``` -但上下文不是普通内存页面。父 Agent 的历史里混杂着用户意图、临时推理、工具日志、权限决定和已经过期的探索路径。完整继承虽然方便,却也会把噪声、错误假设和 token 成本一起复制给 child。 +然而,LLM 的上下文并非等价于平坦的只读内存页。父 Agent 的历史会话中可能交织着原始用户指令、中间回复、工具调试输出、鉴权事件及已淘汰的试错路径。全量继承上下文虽然能免去前置的任务提炼,但也会将大量历史噪声、错误先验及冗余 Token 开销无差别倾倒给子任务。 -因此,multi-agent 系统面对的第一个选择不是“怎样复制得更便宜”,而是“究竟应该复制多少”。 +因此,Multi-Agent 系统面临的首要架构抉择在于确定上下文分叉的边界与粒度。 -## Subagent:不是同事,而是一种 Tool +## Subagent:任务限定的受控工具 -Maka 对这个问题给出了一个激进答案:subagent 不自动继承父 Agent 的对话历史。 +Maka 在上下文继承上采取了严格的边界策略:Subagent 默认不继承父级会话的完整历史。 -主 Agent 调用 `agent_spawn` 时,需要提交一个边界明确、自包含的任务: +主 Agent 调度 `agent_spawn` 时,必须提供一份边界封闭、语义自洽的任务规范: ```text agent_spawn({ @@ -87,9 +87,9 @@ agent_spawn({ }) ``` -Runtime 创建独立的 child Session,为它注入角色、工具、权限和 workspace 边界。child 的第一次模型调用从自己的历史开始,只看到主 Agent 显式交付的任务,而不是父会话的全部过程。 +Runtime 据此创建拥有独立会话历史的子实例,并注入角色指令、受限工具集、权限边界与工作区范围。子 Agent 不会获得父级的完整对话;其初始上下文由运行时指令与显式任务规范共同构成,从而将无关的父级历史排除在工作集之外。 -这要求主 Agent 把隐含上下文编译成一份可以独立执行的 specification: +这要求主 Agent 将全局隐式上下文提炼为可独立执行的契约定义: ```text 调查 packages/storage 中的并发写入机制。 @@ -101,28 +101,28 @@ Runtime 创建独立的 child Session,为它注入角色、工具、权限和 4. 只做只读调查,不修改代码。 ``` -主 Agent 和 subagent 是否需要交流?Maka 的答案是:不需要持续交流。 +在主 Agent 与 Subagent 之间,Maka 舍弃了持续性的双向会话交互: ```text Main Agent ── task ──> Subagent -Main Agent <─ result ─ Subagent +Main Agent <── result ── Subagent ``` -双方之间没有 mailbox,也没有执行到一半回来协商下一步的消息协议。主 Agent 负责拆解问题、选择执行者和综合结果;subagent 只负责完成局部任务。运行过程中的事件可以投影到 UI 供用户观察,但这种 presentation 不是 Agent 之间的 conversation。 +两者之间不存在共享 Mailbox,亦不支持运行期动态磋商指令。主 Agent 专职于顶层目标拆解、算子选型与结果综合,Subagent 聚焦于封闭局部任务的执行。运行时产生的执行事件可投影至前端界面供用户审查,但这属于系统监控层面的单向遥测,不构成 Agent 之间的交互信道。 -从调用者看来,subagent 仍然遵守 Tool 的契约:输入一个任务,执行一个受限过程,返回状态、摘要和 artifact 引用。 +在调用模型视角下,Subagent 遵循工具契约规范:接收结构化任务入参,在分配给它的运行时边界内执行,终态返回结构化状态、摘要文本及产物引用(Artifact Ref): ```text result = subagent(role, tools, task, workspace) ``` -这让上下文边界非常清楚,却也引出了下一个问题:如果任务之间存在复杂依赖,而 child 之间又不通过交谈协调,系统用什么表达全局计划? +该模式确保了清晰的执行边界,同时也引出了核心架构问题:当子任务之间存在严密的因果依赖,而执行单元之间又缺少动态对话信道时,系统应如何刻画并推进全局执行计划。 -## DAG:数据库如何把意图变成执行 +## DAG:关系引擎的状态演进模型 -当计算由多个相互依赖的步骤组成时,最自然的表达通常不是列表,而是 Directed Acyclic Graph,也就是 DAG。 +处理具备依赖拓扑的多阶段计算时,有向无环图(Directed Acyclic Graph,DAG)是最为严谨的拓扑表达形式。 -列表给出全序:先 A,再 B,再 C。DAG 表达的是偏序:边只声明必要的先后关系,没有依赖关系的节点可以自由并发。 +平坦的线性列表强制执行全序调度(先 A,再 B,后 C)。DAG 则定义了偏序关系:边仅用于约束不可逾越的前置依赖,不存在连接关系的节点可获得天然的并发执行自由度: ```text A ───────> C @@ -130,15 +130,15 @@ A ───────> C B ───────> D ``` -这里 A 必须先于 C,B 必须先于 D,但 A 与 B 之间没有天然顺序。调度器不需要获得一份完整执行序列,只需要找到当前输入条件已经满足的节点。 +在此拓扑中,A 构成 C 的前置依赖,B 构成 D 的前置依赖,而 A 与 B 之间互不干扰。调度器无须预先推导串行线性流水线,只需持续识别当前入度已清零(输入依赖已全部就绪)的活跃节点: ```text -Node = 一个计算单元 -Edge = 依赖或数据流 -Ready = 节点的输入条件已经满足 +Node = 计算单元(Operator) +Edge = 依赖约束或数据流向 +Ready = 前置输入条件全部达成 ``` -数据库很早就把“要做什么”与“怎样执行”分成了不同层次。用户提交 SQL,数据库首先生成 logical plan: +现代数据库系统早已将意图定义与物理执行严格分层。用户下发声明式 SQL 后,引擎首先构建逻辑执行计划(Logical Plan): ```text Aggregate by region @@ -150,9 +150,9 @@ Ready = 节点的输入条件已经满足 Scan orders Scan customers ``` -Logical plan 描述关系语义。优化器可以下推 Filter、裁剪列、调整 Join 顺序或简化表达式,只要不改变查询结果。 +逻辑计划专职描述关系代数语义。查询优化器可在保障等价语义的前提下,执行谓词下推、列裁剪、Join 重排及表达式折叠。 -随后,physical planner 把抽象 operator 降低为具体实现: +随后,物理计划生成器(Physical Planner)将抽象逻辑算子降级为底层的工程实现: ```text FinalHashAggregateExec @@ -168,19 +168,19 @@ Logical plan 描述关系语义。优化器可以下推 Filter、裁剪列、调 ParquetScanExec ParquetScanExec ``` -Physical plan 开始决定 Join 算法、partition 数量、并行度以及中间数据是否需要 exchange。同一个 logical plan 可以因为数据规模、分区方式、可用内存和机器核数不同而产生不同的 physical plan。 +物理计划负责敲定具体的 Join 算法、分区哈希策略、并发度及跨节点 Exchange 开销。相同的逻辑关系拓扑会依据数据倾斜度、集群拓扑与内存配额,编译出各异的物理计划。 -但 physical plan 仍然不是执行。运行时还要为节点创建状态、分配资源,让数据流动,并处理结束、取消、错误和 backpressure。 +物理计划确立后,运行时引擎仍需实例化具体的执行流水线,分配内存池与计算配额,推进批流交互,并统一处理终止、超时、异常与反压信号。 -可以立即消费和产出 batch 的 operator 能形成 pipeline: +能够就地流式处理批数据的算子构成连续流水线: ```text Scan ──batch──> Filter ──batch──> Project ──batch──> Sink ``` -Sort、Hash Join 的 build side 或全局 Aggregate 往往必须先积累输入,因而成为 pipeline breaker。到了这一层,执行引擎才真正需要决定哪些 pipeline 已经 ready,以及上下游怎样并发推进。 +而全局排序、Hash Join 的构建端(Build Side)或全局聚合等算子,由于必须完全吸收前置输入方可输出数据,构成了天然的管线断点(Pipeline Breaker)。执行引擎据此切分物理阶段,精准裁决各个子管线的调度准入与并发水位。 -Apache Arrow Acero 展示了一个紧凑的实现:`Declaration` 描述准备构造的节点,`ExecPlan` 和 `ExecNode` 表示一次运行的物理图,`ExecBatch` 是沿边流动的数据。 +Apache Arrow Acero 提供了极为紧凑的工业参考:`Declaration` 抽象声明节点蓝图,`ExecPlan` 与 `ExecNode` 承载单次运行的物理执行图,`ExecBatch` 则作为沿边传递的标准化数据单元。 ```text SQL @@ -198,23 +198,25 @@ Physical Plan Running Pipelines ``` -数据库留下的核心经验是:DAG 不是执行本身,而是一种允许系统逐层优化、降低并最终调度执行的中间表示。 +关系引擎的核心沉淀在于:DAG 本身是中间表达(IR),其核心价值在于支撑系统的逐层变换、语义优化、成本评估与底层确定性调度。 -## Maka Agent Graph:让 Agent 写计划,让系统推进计划 +## Maka Agent Graph:声明式计划与系统驱动推进 -数据库通常能在执行前构造相对完整的 physical plan,Agent 的计划却很难一次写完。 +关系引擎通常能在执行启动前生成封闭确定的物理计划,而 Agent 的认知与执行计划往往高度依赖中间反馈,无法一次性静态穷举。 -一次调查可能暴露出新的问题;一个实现结果可能改变验证方案;某个节点失败后,主 Agent 也可能选择另一条路径,而不是机械重试。Maka 的 Agent Graph 因此是一张在运行过程中逐步生长的 DAG。 +初步的探索性分析可能揭示未知代码分支,局部的实现产物可能彻底改变后续的验证策略,而子任务的执行中断亦会促使规划者切换架构备选方案。Maka 的 Agent Graph 因此被建模为一张在会话生命周期内持续演进的动态 DAG。 -它把职责分成三部分: +该机制在职责切分上确立了三权分立: -> 主 Agent 负责写计划,Coordinator 负责推进计划,Supervisor 负责观察计划。 +- **主 Agent**:专职规划业务蓝图与语义决策。 +- **Coordinator**:专职推进依赖解析与拓扑收敛。 +- **Supervisor**:观察执行图的关键检查点,并在工作流需要语义判断时恢复主 Agent 的决策过程。 ### 主 Agent 写入 Durable Intent -只有 root Session 中的主 Agent 拥有 Graph control tools。它可以追加工作、停止或替换旧工作,并选择最终结果关闭 Graph。child Session 不能反向修改全局拓扑。 +在 Maka 中,仅 Root Session 的主 Agent 具备全局执行图的操作权限。主 Agent 负责登记算子定义、派发依赖任务、标记废弃旧节点,并选取收敛产物终结执行图;子会话被剥离了篡改全局拓扑的系统权限。 -主 Agent 通过 `update_agent_graph` 提交 schedule revision。没有输入依赖的 work 可以并行;后续 work 则引用 upstream 已提交的 result record: +主 Agent 借助 `update_agent_graph` 提交结构化的调度修订(Schedule Revision)。无前置依赖的任务自动并发,后续任务显式锚定上游已落盘的产物记录: ```text Runtime review result ─┐ @@ -222,13 +224,13 @@ Runtime review result ─┐ Storage review result ─┘ ``` -这不是“现在启动三个进程”的瞬时命令,而是 durable intent:系统要增加什么 work、输入 frontier 是什么、谁应该被停止或替换,以及最终选择哪些结果。 +该调用提交的是持久化执行意图(Durable Intent):清晰阐明待追加的工作单元、输入前沿状态、待熔断或替换的历史分支,以及最终采纳的输出集合。 -Schedule update 以 append-only revision 提交到 SQLite,并带有来源 Session、Run、Turn 和 Tool Call identity。主 Agent 即使退出,已经写下的计划也不会随着模型上下文消失。 +每次调度更新均以追加写日志(Append-Only Revision)的形式持久化至 SQLite,并严格打上宿主会话、Run、Turn 与 Call ID 的审计标记。即便主 Agent 进程遭遇崩溃,已提交的执行计划亦绝不会在内存挥发中遗失。 -### Coordinator 是 Reconciler +### Coordinator 专职状态对齐 -Coordinator 不在内存中长期持有一份权威的可变 DAG。每轮 reconciliation 都重新读取持久状态: +Coordinator 并不在内存中维护长期易失的权威 DAG 对象。每次执行对齐(Reconciliation)均从持久化存储中重新提取基线事实: ```text SQLite control plane @@ -242,7 +244,7 @@ SQLite control plane Coordinator reconstructs a snapshot ``` -它把 revisions 折叠成当前计划,把 provisions 组成 topology,再结合 AgentRun 和 committed RuntimeEvents,计算哪些 work 已经完成、哪些输入仍未出现,以及哪些节点已经 ready。 +Coordinator 将追加写的修订事件折叠为当前计划快照,结合已注册的算子定义构建拓扑,再结合各子任务已持久化的 `RuntimeEvent`,计算各节点的终态与就绪边界。 ```text Observe durable state @@ -263,11 +265,11 @@ Claim exact Turn / Run identities Dispatch child AgentRuns ``` -Maka 当前使用事件驱动的 single-flight driver,而不是固定 `setInterval` 扫描数据库。新的 schedule、child RuntimeEvent 或 host recovery 都可以请求 reconciliation;同一个 Graph 同时只有一个 driver,重复 wake 被合并到下一轮。 +Maka 采用事件驱动的 Single-Flight Driver,而非固定间隔的数据库轮询循环。新的调度提交、子任务事件回传或宿主恢复均可拉起对齐循环;单张执行图在任意时刻仅允许一个 Driver 实例活跃,重复的并发唤醒请求自动合并入后续轮次。 -### SQLite 是 Control Plane +### SQLite 担任控制面存储 -Graph 没有再造第二套 Agent runtime。SQLite 只保存调度事实,实际的模型调用、Tool Call、权限处理、停止和 RuntimeEvent 持久化仍由 Session Runtime 完成。 +Agent Graph 避免重复造一套平行的 Agent 运行时。SQLite 专职托管全局调度事实,底层实际的模型交互、工具调用、权限审查、异步中断及事件持久化,全量复用成熟的 Session Runtime。 ```text Main Agent ──> SQLite schedule @@ -282,13 +284,13 @@ Main Agent ──> SQLite schedule committed RuntimeEvents ``` -一个 child Session 是稳定的 operator container,一次 AgentRun 是一次 activation,已经提交的 RuntimeEvent 才能成为 Graph 中可消费的 record。 +在系统模型中,子会话充当稳定的算子容器,`AgentRun` 对应单次执行生命周期。下游节点消费的是由运行时事件支撑、已经提交的结果记录,并非将每一条 `RuntimeEvent` 都直接视为执行图输入。 -### Claim 把 Ready 与 Execute 分开 +### Claim 机制解耦就绪计算与执行准入 -Coordinator 可以反复重建 snapshot,因此同一个节点也可能被多次计算为 ready。如果看到 ready 就直接调用模型,崩溃和重试可能导致重复执行。 +由于 Coordinator 在每次对齐时重建快照,同一个算子节点可能在不同的运算切片中被反复确认为 Ready。若系统在探知就绪时直接触发模型调用,在遭遇重试或瞬时调度抖动时极易产生重复执行。 -Maka 在执行前向 SQLite 写入 conditional claim,把确定性 intent 绑定到具体 operator、Session、Turn 和 Run identity: +Maka 在实际拉起子任务前,向 SQLite 写入前置条件认领记录(Conditional Claim),将处于 Ready 状态的抽象意图严密绑定至具体算子、会话编号、交互轮次及执行 ID: ```text ready intent @@ -300,13 +302,13 @@ conditional claim └── new claim ───────> execute the allocated Run ``` -Readiness 是可以重复计算的 projection,execution admission 则成为持久事实。 +就绪判定(Readiness)属于可无副作用反复推导的投影视图,而执行准入(Execution Admission)则作为权威事实被一次性原子落盘。 -### Supervisor 在 Checkpoint 处恢复判断 +### Supervisor 于检查点恢复语义把关 -Coordinator 能确定性推进计划,却不适合判断两份调查是否矛盾,或者一次失败应该重试、替换还是改变方向。这些语义决策仍然属于主 Agent。 +确定性的 Coordinator 擅长依据拓扑推进执行,却无法代替大语言模型进行高维语义裁决:评估两份调查结论是否存在逻辑矛盾、判断子任务的执行挫折应采取重试、替换算子亦或彻底推倒既有路线。这些关键决策依赖于主 Agent 的高阶推理。 -主 Agent 写完一轮 schedule 后,可以结束当前 supervisor turn。Coordinator 异步推进 Graph;到达 durable checkpoint 后,Host 再创建一个新的 supervisor turn: +主 Agent 提交完一轮调度规范后,即刻结束当前的监督轮次(Supervisor Turn)。Coordinator 在后台异步调度并发子流水线;一旦全局执行图抵达持久化检查点(Durable Checkpoint),Host 环境唤醒主 Agent 进入全新轮次: ```text Main Agent schedules work @@ -321,19 +323,19 @@ durable checkpoint Host wakes Main Agent ``` -主 Agent 读取有界 Graph snapshot,必要时读取 child 的 committed result,然后增加下一轮工作、停止或替换旧 work,或者选择结果 finish Graph。 +主 Agent 读取有界的执行图快照与下游提交的产物正文,启动下一阶段任务规划、剔除陈旧节点或终结图任务。 -整个闭环中存在两种智能:主 Agent 提供拆解、判断和综合的语义智能;Coordinator 提供持久化、拓扑重建、并发推进和故障恢复的系统智能。 +全流程形成了两种系统能力的互补结合:主 Agent 贡献语义维度的拆解与仲裁,Coordinator 贡献持久化状态机、确定性拓扑演算、并发编排及故障自愈等系统层面的控制保证。 -## Go Channel:通信本身就是调度 +## Go Channel:基于通信范式的并发调度 -DAG 能描述依赖,却不能独自回答执行时的等待、唤醒与背压。Go 的并发模型提供了另一个观察调度的角度。 +DAG 擅长固化静态与动态的宏观依赖,但在微观层面仍需解决任务挂起、事件唤醒与流控反压等调度细节。Go 语言的并发哲学为多智能体调度提供了另一套经典的系统视角。 -goroutine 是由 Go runtime 调度的轻量级执行单元。常被概括为 G-M-P 的 runtime 会把大量 goroutine 多路复用到较少的 OS thread 上:G 表示 goroutine,M 表示 OS thread,P 表示执行 Go 代码所需的 runtime 资源。 +goroutine 是由 Go 运行时自主调度的轻量执行体。经典的 G-M-P 运行时模型将海量的应用协程多路复用至有限的操作系统原生线程:G 代表协程实体,M 对应系统内核线程,P 则抽象了执行 Go 代码所需的逻辑处理器资源。 -goroutine 解决“如何廉价地产生并发任务”,Channel 则解决它们“如何协作”。 +goroutine 实现了极低成本的并发单元实例化,而 Channel 则确立了各单元间的协作契约。 -### 无缓冲 Channel 是 Rendezvous +### 无缓冲 Channel 作为对齐集合点 ```go handoff := make(chan Result) @@ -341,27 +343,27 @@ go func() { handoff <- result }() received := <-handoff ``` -无缓冲 Channel 的发送者等待接收者,接收者也等待发送者。只有双方都到达交接点,通信才能完成。它传递的不只是 `Result`,还传递了“双方已经在这里会合”的同步事实。 +无缓冲 Channel 要求发送端与接收端均已就绪方可完成交接。发送端在接收就绪前阻塞,接收端在数据送达前挂起,因此发送与接收操作构成了两个 goroutine 之间的同步点: -Go memory model 为 Channel 操作定义了 happens-before。接收者拿到值后,可以观察发送者在 send 之前完成的写入。因此 Channel 同时承载了: +Go 内存模型为 Channel 操作赋予了严谨的 Happens-Before 偏序保障。接收端成功提取数据时,能够安全观察到发送端在投递动作前所完成的全部内存写入。因此,单次 Channel 通信复合了多重职责: ```text value transfer + scheduling point + memory ordering ``` -### Buffer 定义允许领先的距离 +### 缓冲区界定解耦容限 ```go jobs := make(chan Job, 32) ``` -有缓冲 Channel 允许生产者和消费者在有限距离内解耦。只要还有空间,send 就能继续;buffer 满以后,生产者阻塞,压力沿 pipeline 反向传播。 +有缓冲 Channel 允许生产者与消费者在受控深度内异步解耦。只要缓冲区存在未填满槽位,发送操作即可无阻塞完成;一旦缓冲耗尽,发送协程挂起,调度压力沿调用链逆向回溯。 -容量 `32` 不只是性能参数,也规定生产者最多可以比消费者领先多少项工作。太小会损失并行度,太大则可能积压过期任务、放大内存占用,并延迟暴露下游变慢的问题。 +容量阈值不仅是性能调优参数,更界定了上游被允许领先下游的最大工作配额。容量过紧会削弱流水线的吞吐平滑度;容量过大则会导致过期任务堆积、内存水位失控,并掩盖下游算子已经发生严重退化的真实瓶颈。 -### `select`、`close` 与 nil Channel +### 多路复用与生命周期信令 -`select` 允许一个 goroutine 同时等待多条通信边: +Go 通过 `select` 语法允许单个执行体同时监听多组通信边: ```go select { @@ -372,15 +374,15 @@ case <-ctx.Done(): } ``` -它是一种调度接口:执行单元声明自己依赖哪些事件,runtime 在其中一项 ready 时恢复它。 +该结构充当声明式的调度接口:执行单元声明其关心的前置信号集,运行时在任一条件达成时将其从等待队列唤醒。 -`close(ch)` 发布的是“不会再有新值”的生命周期状态。关闭后,接收者先读完 buffer,再通过 `value, ok := <-ch` 观察结束。关闭还可以充当广播,因为所有等待者都能观察到它。 +`close(ch)` 则是对生命周期终止状态的广播分发。通道关闭后,接收端在排空既有缓冲后,可通过 `value, ok := <-ch` 感知到通道已完结。所有阻塞在空且已关闭通道上的接收者都能观察到这一终态。 -nil Channel 则永远不会 ready。在 `select` 中把某个 Channel 变量设为 nil,可以动态禁用一条分支,构造小型并发状态机。 +nil Channel 则具备永不就绪的物理特性。在 `select` 块中动态将某分支的通道变量置为 nil,可在不破坏调度主循环的前提下优雅禁用特定分支,构成高内聚的状态机控制。 -### Pipeline 必须拥有取消路径 +### 流水线构建中的取消传播 -多个 stage 可以由 Channel 连接成 pipeline,也可以通过多个 goroutine 形成 fan-out 和 fan-in。但如果下游提前退出,上游可能永久阻塞在 send 上并泄漏 goroutine。 +多个计算阶段可借助 Channel 串联为处理流水线,亦可通过扇出(Fan-out)与扇入(Fan-in)实现多路并行。然而,下游算子若非正常提前退场,未受保护的上游生产者将永久阻塞于发送点,导致协程泄漏。 ```go select { @@ -390,21 +392,21 @@ case <-ctx.Done(): } ``` -每个可能长期阻塞的 send 或 receive,都应该回答:如果另一端永远不会再出现,这个 goroutine 怎样退出? +任何可能比下游消费者存活更久的阻塞通信点,都需要显式的取消路径,使相关 goroutine 能够安全清理并退出。 -Go Channel 的真正特色,是没有把数据流与控制流彻底分开。一次通信既传递 value,也表达 dependency、synchronization 和 backpressure: +Go Channel 的核心架构特征在于将数据载荷与调度语义高度复合:一次通信行为同时承载了数据交换、依赖表达、状态同步与反压流控: ```text communication = dependency + synchronization + backpressure ``` -这个模型也启发了另一派 subagent 系统:如果每个 Agent 都拥有一只 inbox,消息到达本身是否可以成为调度条件? +这一模型启示了另一种 Multi-Agent 系统设计方案:若为每个 Agent 分配专属收件箱,消息投递行为本身能否直接充当系统调度的核心驱动力。 -## Codex Subagent:用 Mailbox 组织协作 +## Codex Subagent:基于私有信箱的协同架构 -Codex 给出的答案是肯定的。它保留 parent-child delegation,同时把每个 Agent 建模成有身份、有独立历史、可以持续接收消息的执行单元。 +Codex 在其 Subagent 协作演进中给出了另一种实践。它保留了父子任务委派关系,同时将每个 Agent 建模为可在同一根任务树内寻址、持有独立会话历史、依托私有队列异步收发消息的实体。 -同一棵 subagent tree 中的 Agent 拥有可寻址路径: +处于同一根任务树中的 Agent 共享一套层级化寻址路径: ```text /root @@ -414,7 +416,7 @@ Codex 给出的答案是肯定的。它保留 parent-child delegation,同时 └── /root/test_runner ``` -这套模型与 Actor system 很接近: +该拓扑高度契合经典的 Actor 系统模型: ```text Actor identity = AgentPath @@ -423,9 +425,9 @@ Actor mailbox = Session InputQueue Actor activation = Turn ``` -### Mailbox 是 Session 的私有队列 +### 信箱作为会话内部的私有队列 -Codex Core 的 `InputQueue` 把 payload 与 wakeup 分开: +Codex 核心的 `InputQueue` 结构将数据存储与唤醒信令彻底解耦: ```rust struct InputQueue { @@ -434,22 +436,22 @@ struct InputQueue { } ``` -`VecDeque` 保存 FIFO 消息,Tokio `watch` Channel 通知等待者 mailbox 发生变化。通知可以合并,因为 queue 才是消息事实来源。 +内存中的 `VecDeque` 在会话存续期间维持消息的先进先出(FIFO)顺序,而 Tokio 的 `watch` 通道则向监听调度器下发信箱发生变动的轻量唤醒脉冲。多条变动信号可以合并,因为待处理负载仍保留在队列中;这个队列本身并不意味着持久化。 -这不是多个 worker 竞争 claim 的共享 inbox。每个 Session 都有自己的 mailbox,每封 `InterAgentCommunication` 在投递前就已经指定 `author`、`recipient`、`content` 和 `trigger_turn`。 +这并非多个无差别工作节点共同竞争的任务拉取队列。每个会话独占自身的专有信箱,每封通信记录在派发前必须预先绑定发送方(Author)、接收方(Recipient)、文本负载及是否拉起轮次的标志位(`trigger_turn`)。 -### Message 也携带调度意图 +### 消息附带显式调度意图 -Codex V2 区分两种投递: +Codex V2 将消息投递清晰划分为两种调度级别: ```text send_message = QueueOnly followup_task = TriggerTurn ``` -`send_message` 只把消息放进目标 inbox。目标 Agent 正在运行时,它会在后续模型边界看到消息;目标 Agent 已经空闲时,消息等待下一次自然 activation。 +`send_message` 仅执行入队追加。若目标 Agent 正在执行,信件将在后续的模型推理切片边界被统一查阅;若目标 Agent 处于休眠,消息在信箱中静默封存,等待未来的自然交互周期。 -`followup_task` 会设置 `trigger_turn=true`。如果目标 Agent 已经空闲,pending-work scheduler 可以为它创建新的 Turn。 +`followup_task` 则显式注入 `trigger_turn = true` 属性。若目标 Agent 处于空闲等待状态,待办调度器获准为其即时创建新的执行轮次。 ```text InterAgentCommunication @@ -461,11 +463,11 @@ followup_task = TriggerTurn queue message wake idle Agent ``` -所以一封消息同时表达 information、recipient 和 scheduling intent。 +单次消息投递同时复合了信息传递、目标寻址与执行调度意图。 -### Agent 在模型边界收信 +### 模型采样边界的受控收信机制 -消息不会修改一次已经发出的 LLM sampling request。它先进入 mailbox,等待 Turn loop 再次构造模型 context: +外部消息绝不会破坏性中断一次正在进行的 LLM 采样请求。新抵达的消息先入队隔离,等待当前交互轮次结束、重新构建下一次模型上下文时批量水合入局: ```text Agent B starts sampling @@ -484,15 +486,15 @@ Agent A sends a message build next model request ``` -Codex 还维护 `MailboxDeliveryPhase`。Turn 开始时,邮件可以加入当前执行;一旦 runtime 已经记录用户可见的 final answer,迟到消息就被留给下一轮,避免已经结束的答案被后台消息悄悄续写。 +Codex 引入了 `MailboxDeliveryPhase` 状态机。轮次启动初期,信箱积压邮件被允许注入上下文;一旦运行时已在本地生成并记录了面向用户的最终输出,迟到的后发消息将被强制顺延至后续轮次,严防已敲定的结论被并发涌入的次级消息无序篡改。 -### Completion 本身也是一封消息 +### 任务完结作为结构化消息回传 -Codex 为 child 启动 completion watcher。child 进入终态后,watcher 构造一封从 child 发往 parent 的 `InterAgentCommunication`,并把它放进 parent mailbox。 +Codex 为子任务配置了专属的状态观察者(Completion Watcher)。子任务推进至最终稳态后,观察者构造一条由子任务发往父任务的通信记录,并安全置入父任务信箱。 -这封 completion message 的 `trigger_turn` 是 false。结果首先是一条进入 parent inbox 的事实,而不是强制 parent 立即推理的中断。 +此完结信件的 `trigger_turn` 默认配置为 `false`。执行结果首先作为一条事实沉淀入父级收件箱,避免非预期打断父任务可能正在专注展开的上下文。 -`wait_agent` 因此也不直接从指定 child 拉取正文。它订阅当前 Session 的 mailbox activity: +`wait_agent` 工具通过等待父会话的信箱活动来实施流控,而不是直接返回某个子任务的结果: ```text wait_agent @@ -502,11 +504,11 @@ wait_agent └── deadline ─────> timeout ``` -工具只负责 suspend 和 wakeup;消息正文仍保存在 mailbox 中,随后由 Turn loop 放进模型 context。 +该工具仅负责执行挂起与唤醒协议;实际的结果内容仍保留于信箱底层,在随后的正常轮次中并入模型上下文。 -### Workflow 在 Conversation 中展开 +### 对话交织中演进的工作流 -DAG 系统把依赖写成显式边,Codex 的协作则可能表现为一串动态消息: +基于 DAG 的体系将所有依赖固化为显式拓扑边,而基于 Mailbox 的体系则呈现为一系列在时间线上展开的动态消息流: ```text Root ──task──────> Agent A @@ -517,49 +519,47 @@ Root ──follow-up─> Agent A Agent A ──result─> Root ``` -Agent A 可以把新发现立即告诉 Agent B,主 Agent 也可以在 child 尚未完成时补充约束。实际 workflow 不必预先完整存在,而是在 conversation 中逐步生长。 +Agent A 在得出局部洞察后可即时知会并发的 Agent B,主 Agent 亦能在子任务运行中途随时追加补充指引。真实的业务工作流无须在初始阶段完成全量预测,而是在多方对话交互中逐步成型。 -灵活性也带来代价。控制流分散在消息历史中;想解释 Agent 为什么改变方向,需要回放它收到的 mailbox;想知道一项工作何时真正 ready,也不能只数一张全局 DAG 的入边。 +动态灵活性对应着系统复杂度的转移:系统控制流被分散掩埋在跨 Agent 的通信历史中。追溯某个决策变更的原因需要完整重放对应信箱的吸收序列;研判某个中间节点是否已满足执行条件,亦无法简单依赖全局有向图的拓扑入度来决断。 -Codex 因而可以概括为:以主 Agent 委派为入口,以 Actor mailbox 作为协作平面。 +Codex 架构的技术本质可概括为:在根任务范围内保留父子委派关系,并在其上叠加 Actor 风格的私有信箱协作层。 -## 总结:Workflow 与 Collaboration +## 架构选型:工作流编排与消息协同 -Maka 和 Codex 都能创建多个 subagent,也都支持并行和 follow-up,但它们选择了不同的系统原语。 +Maka 与 Codex 均支持 Subagent 派生、并发执行及追加任务,但在系统底层原语的设计上做出了截然不同的取舍。 -| 维度 | Maka workflow | Codex mailbox | -| -------- | ------------------------------ | -------------------------------------- | -| 核心抽象 | DAG 中的 operator 与 edge | 可寻址 Agent 与私有 inbox | -| 任务产生 | 主 Agent 写 schedule | parent spawn 或 Agent 发送 follow-up | -| 调度条件 | Coordinator 计算节点 readiness | 消息到达、`trigger_turn` 与 Agent 状态 | -| 数据传递 | record 沿依赖边成为下游输入 | message 进入目标 Agent context | -| 横向交流 | child 之间不需要通信 | Agent 可以向其他 Agent 发消息 | -| 状态观察 | 读取全局 Graph snapshot | 检查各 Agent 状态并消费 inbox | -| 主要优势 | 显式、可审计、容易确定性恢复 | 灵活、可动态协商、适合未知路径 | -| 主要代价 | 临时协商必须回到 Graph | 控制流隐含,消息与 context 容易膨胀 | +| 维度 | Maka 工作流模型 | Codex 信箱协作模型 | +| ------------ | ---------------------------------------- | ------------------------------------------ | +| **核心抽象** | DAG 拓扑中的算子与数据边 | 可在根任务树内寻址的 Agent 与私有信箱 | +| **任务派发** | 主 Agent 显式编写调度计划 | 父级派发或对等实体投递 Follow-up | +| **调度条件** | Coordinator 演算节点依赖就绪度 | 消息入队、`trigger_turn` 标记及活跃度 | +| **数据传递** | 结构化记录沿依赖边注入下游 | 消息体在模型轮次边界并入会话上下文 | +| **横向交互** | 不向子节点暴露直接通信通道 | 根任务树内的 Agent 支持直接消息传递 | +| **全局视图** | 随时可提取全局确定性图快照 | 需汇聚各 Agent 状态与信箱积压综合反推 | +| **核心优势** | 显式拓扑、确定性执行、原生易于审计与恢复 | 具备极高弹性、支持动态协商、适配开放式探索 | +| **核心代价** | 动态调整需重新提请修改调度图 | 控制流隐式离散,消息与上下文极易膨胀 | -Workflow 适合依赖明确、结果可结构化、执行时间较长并且必须可靠恢复的任务。代码扫描、批量测试、数据处理和多阶段 research synthesis 都可以被建模为 operator 与 record。 +工作流模型适用于前后依赖清晰、产物结构规范、单次执行跨度较长且对崩溃恢复要求极高的系统级工程任务。静态代码分析、并行构建测试、大规模数据管道及多阶段技术调研均具备明确的“算子与产物”特征。 -Mailbox 适合下一步取决于语义发现、角色需要不断交换信息、计划无法预先穷举的任务。设计讨论、交叉审查和开放式调查更接近这种协作。 +信箱协同模型更适用于行动分支高度取决于即时语义发现、各角色需密集双向推演、且全流程无法预先穷举的探索性任务。系统方案研讨、交叉代码审查与发散式安全渗透更贴合此类去中心化的交互结构。 -两者真正的分界并不是“是否使用 subagent”,而是把协调状态放在哪里: +两种路径的底层分水岭在于协调状态(Coordination State)的物理托管位置: -```text -Maka: coordination lives in the Graph -Codex: coordination lives in the Conversation -``` +- **Maka**:将协同状态上浮收拢于全局执行图(Coordination lives in the Graph)。 +- **Codex**:将协同状态离散下沉于多方通信流(Coordination lives in the Conversation)。 -Graph 把计划从模型上下文中提取出来,交给确定性系统推进;Conversation 保留 Agent 的交流自由,让计划在运行中自然形成。前者更像数据库执行引擎,后者更像 Actor system。 +Agent Graph 将计划从大模型的易失上下文中抽离,交由持久、可重放的系统协调器推进;Mailbox 机制则赋予智能体更自由的动态调整空间。前者更贴近确定性的关系数据库计算引擎,后者则深植于经典的 Actor 并发框架。 -这也解释了为什么 Maka 刻意不让 subagent 互相聊天。它并不是认为 Agent 无法协作,而是选择把协作编译成显式的 schedule:模型负责语义判断,Runtime 负责执行事实,Coordinator 负责推进依赖。 +这也阐释了 Maka 约束 Subagent 横向直接交流的设计取向:将协同过程静态编译为透明受控的调度图。大模型专职贡献高价值的语义逻辑判断,Runtime 严密记录真实系统的执行事实,而 Coordinator 则负责确定性地驱动数据依赖向前收敛。 -Multi-agent 调度最终不是“启动多少模型”的问题,而是一个更传统的系统问题:如何表示状态,如何传递依赖,如何控制并发,以及在任何一个执行者退出以后,系统还能否知道下一步该做什么。 +Multi-Agent 系统的核心本质是一套经典的分布式系统课题:状态如何规范表达、依赖如何可信传递、并发如何精准约束,以及当特定执行节点遭遇崩溃退出后,整个系统是否依然具备清晰的前进确定性。 ## 延伸阅读 - [Linux `fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html) -- [Apache DataFusion:Reading Explain Plans](https://datafusion.apache.org/user-guide/explain-usage.html) -- [Apache Arrow:Acero Overview](https://arrow.apache.org/docs/cpp/acero/overview.html) +- [Apache DataFusion: Reading Explain Plans](https://datafusion.apache.org/user-guide/explain-usage.html) +- [Apache Arrow: Acero Overview](https://arrow.apache.org/docs/cpp/acero/overview.html) - [The Go Programming Language Specification: Channel types](https://go.dev/ref/spec#Channel_types) - [The Go Memory Model](https://go.dev/ref/mem) - [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines)