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..a22303f22a --- /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 + +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 trade-offs divide multi-agent systems into two distinct evolutionary paths. + +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 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: Branching State on Mutation + +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()` 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 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 ───┘ +``` + +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 ───┘ + +After child writes + +Parent ─────> Page A +Child ─────> Page A' +``` + +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. + +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 + │ + ┌────┴────┐ + ▼ ▼ + Main delta Child delta +``` + +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. + +Multi-agent architectures must therefore define the exact boundary and granularity of context propagation. + +## Subagents as Task-Scoped Tools + +Maka enforces a strict isolation boundary: subagents never automatically inherit the full conversation history of the parent agent. + +When the main agent invokes `agent_spawn`, it must supply a self-contained task specification: + +```text +agent_spawn({ + subagent_id: "local-reader", + task: "Inspect how the storage package handles concurrent writes, citing files and symbols" +}) +``` + +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. + +This contract requires the main agent to compile implicit conversational context into a standalone specification: + +```text +Investigate concurrent write handling in packages/storage. + +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. +``` + +Maka eliminates ongoing conversational chatter between the main agent and its subagents: + +```text +Main Agent ── task ──> Subagent +Main Agent <── result ── Subagent +``` + +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 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) +``` + +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: The Relational Engine Execution Model + +Directed Acyclic Graphs (DAGs) provide a rigorous structural representation for multi-stage computation involving dependency constraints. + +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 + +B ───────> D +``` + +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 = Computational unit (Operator) +Edge = Dependency constraint or data flow +Ready = All inbound preconditions satisfied +``` + +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 + │ + Join + ┌────┴────┐ + Filter Project + │ │ + Scan orders Scan customers +``` + +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. + +The Physical Planner subsequently translates logical operators into concrete engine implementations: + +```text + FinalHashAggregateExec + │ + RepartitionExec + │ + PartialHashAggregateExec + │ + HashJoinExec + ┌─────┴─────┐ + FilterExec RepartitionExec + │ │ + ParquetScanExec ParquetScanExec +``` + +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. + +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 consume and produce record batches in a streaming manner combine into execution pipelines: + +```text +Scan ──batch──> Filter ──batch──> Project ──batch──> Sink +``` + +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 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 + │ parse / analyze + ▼ +Logical Plan + │ semantic optimization + ▼ +Optimized Logical Plan + │ physical planning + ▼ +Physical Plan + │ instantiate / schedule + ▼ +Running Pipelines +``` + +Relational engines demonstrate that a DAG functions primarily as an Intermediate Representation (IR), enabling systematic optimization, lowering, and deterministic scheduling. + +## Maka Agent Graph: Declarative Planning and Engine Progression + +Relational query engines produce deterministic physical plans prior to execution, whereas agent plans evolve dynamically based on runtime discovery. + +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. + +The architecture enforces a strict separation of operational concerns: + +- **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 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 records plan modifications via `update_agent_graph`. Independent nodes run concurrently, while dependent nodes explicitly reference committed upstream artifacts: + +```text +Runtime review result ─┐ + ├──> Synthesis work +Storage review result ─┘ +``` + +This interaction commits Durable Intent: specifying newly provisioned work units, current input frontiers, targeted node deprecations, and terminal outcome selections. + +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 Acts as a State Reconciler + +The Coordinator avoids retaining authoritative in-memory graph objects. Every reconciliation cycle reconstructs baseline truth directly from durable storage: + +```text +SQLite control plane + │ + ├── schedule updates + ├── operator provisions + ├── intent claims + └── supervisor wakes + │ + ▼ +Coordinator reconstructs a snapshot +``` + +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 + │ + ▼ +Apply stop / replace / finish decisions + │ + ▼ +Provision missing operators + │ + ▼ +Resolve ready work + │ + ▼ +Claim exact Turn / Run identities + │ + ▼ +Dispatch child AgentRuns +``` + +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 Serves as the Control Plane + +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 + │ + ▼ + Coordinator + │ claim / dispatch + ▼ + Child Sessions / AgentRuns + │ + ▼ + committed RuntimeEvents +``` + +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 Decouple Readiness from Execution Admission + +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. + +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 + │ + ▼ +conditional claim + │ + ├── already exists ──> inspect or recover the same Run + └── new claim ───────> execute the allocated Run +``` + +Readiness evaluation remains a reproducible, side-effect-free projection, whereas execution admission commits as an atomic, durable fact. + +### The Supervisor Restores Semantic Judgment at Checkpoints + +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 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 + │ + ▼ +Coordinator advances Graph + │ + ▼ +durable checkpoint + │ + ▼ +Host wakes Main Agent +``` + +The main agent inspects bounded graph snapshots and committed child outputs, scheduling subsequent work units, pruning obsolete branches, or marking the graph complete. + +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 as 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 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 provide cost-effective concurrency, while channels establish structured communication contracts between them. + +### Unbuffered Channels as Rendezvous Points + +```go +handoff := make(chan Result) +go func() { handoff <- result }() +received := <-handoff +``` + +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 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 +``` + +### Buffers Regulate Decoupling Capacity + +```go +jobs := make(chan Job, 32) +``` + +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. + +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. + +### Multiplexing and Lifecycle Signaling + +Go provides the `select` statement to allow a goroutine to monitor multiple channel events simultaneously: + +```go +select { +case job := <-jobs: + return handle(job) +case <-ctx.Done(): + return ctx.Err() +} +``` + +The `select` construct functions as a declarative scheduling interface: the worker declares event dependencies, and the runtime awakens it when any condition resolves. + +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 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. + +### Cancellation Propagation in Pipelines + +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 { +case out <- result: +case <-ctx.Done(): + return +} +``` + +Any blocking communication site that may outlive its downstream consumer needs an explicit cancellation path so workers can terminate cleanly and release resources. + +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 +``` + +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: Mailbox-Driven Collaboration + +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 within the same root task tree share a hierarchical addressing namespace: + +```text +/root +├── /root/runtime_review +├── /root/storage_review +│ └── /root/storage_review/query_analysis +└── /root/test_runner +``` + +This architecture closely mirrors the classic Actor model: + +```text +Actor identity = AgentPath +Actor state = Thread history +Actor mailbox = Session InputQueue +Actor activation = Turn +``` + +### Private Mailbox Queues per Session + +The Codex Core `InputQueue` decouples payload storage from wakeup notifications: + +```rust +struct InputQueue { + activity_tx: watch::Sender, + mailbox_pending_mails: Mutex>, +} +``` + +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 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. + +### Messages Convey Scheduling Intent + +Codex V2 differentiates message delivery into two scheduling tiers: + +```text +send_message = QueueOnly +followup_task = TriggerTurn +``` + +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. + +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 + │ + ┌──────────┴──────────┐ + │ │ + trigger_turn = false trigger_turn = true + │ │ + queue message wake idle Agent +``` + +A message transmission simultaneously conveys conversational information, destination addressing, and execution scheduling intent. + +### Controlled Message Ingestion at Model Boundaries + +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 + │ +Agent A sends a message + │ + ▼ + B.mailbox.enqueue + │ + current sampling ends + │ + ▼ + drain mailbox + │ + ▼ +build next model request +``` + +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. + +### Task Completion Delivered as Structured Messages + +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. + +The completion message sets `trigger_turn = false`. Results enter the parent inbox as factual events rather than disruptive interrupts, preserving parent reasoning continuity. + +The `wait_agent` tool waits on parent mailbox activity instead of returning a child result directly: + +```text +wait_agent + │ + ├── new mail ─────> wake + ├── user steer ───> interrupt wait + └── deadline ─────> timeout +``` + +The tool coordinates suspension and wakeups, while message content remains buffered within the mailbox until the turn loop incorporates it into model context. + +### Conversational Workflow Evolution + +DAG systems compile execution dependencies into explicit topological edges, whereas mailbox architectures express workflows through dynamic message exchanges: + +```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 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. + +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. + +The Codex architecture combines root-scoped parent-child delegation with actor-style private mailboxes to form a collaborative coordination layer. + +## Architectural Trade-offs: Workflow Scheduling and Message Collaboration + +Maka and Codex support subagent delegation, concurrent execution, and iterative follow-ups, but diverge fundamentally in their underlying systems primitives. + +| 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 | + +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. + +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 fundamental divergence lies in where coordination state resides: + +- **Maka:** Coordination lives in the Graph. +- **Codex:** Coordination lives in the Conversation. + +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 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 ultimately addresses foundational distributed systems challenges: modeling state, passing dependencies reliably, bounding concurrency, and ensuring predictable forward progress when individual workers terminate. + +## 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..66b2369e64 --- /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 调度的两条路径 + +在 Multi-Agent 架构中,创建 Subagent 常被简化理解为“并行启动多个模型处理任务”。然而,系统设计的核心瓶颈在于调度拓扑:Subagent 应继承何种上下文、任务由谁拆解与分发、执行依赖如何严谨表达、产出结果如何可靠交付、单点故障后如何确定性恢复,以及并发 Agent 之间是否应当存在直接的通信通道。 + +这些工程权衡将 Multi-Agent 系统划分为两条截然不同的演进路径。 + +第一条路径将 Subagent 视为工作流算子(Operator):主 Agent 规划确定性执行图,调度内核依据显式依赖推进执行,数据与产物沿有向边单向流动。第二条路径将 Subagent 视为协作参与者(Participant):每个 Agent 具备独立身份与私有信箱(Mailbox),通过异步消息传递驱动协作,实际控制流在多轮对话中动态交织。 + +Maka 采用了第一条基于显式工作流图的路径,而 Codex 的 Subagent 体系则代表了第二条消息驱动的典型设计。理解这两类架构的前提,在于审视操作系统如何在进程派生中低成本管理状态分叉。 + +## Copy-on-Write:按需分叉的隔离哲学 + +在操作系统设计中,Copy-on-Write(CoW,写时复制)是控制分支状态复制开销的核心机制。Linux 线程默认共享同一虚拟地址空间,而经典 CoW 则作用于 `fork()` 系统调用派生新进程的阶段。 + +若 `fork()` 采取全量深拷贝策略,子进程创建开销将与父进程物理内存呈线性正比。更低效的是,子进程通常会迅速调用 `exec()` 载入新二进制映像,导致复制的大量内存页未被访问即遭丢弃。 + +Linux 通过建立彼此独立、但初始指向相同物理页帧的页表来延迟物理拷贝。`fork()` 触发时,内核为子进程建立独立的虚拟地址空间,让对应页表项映射同一组物理页,并将原本私有可写的映射标记为只读: + +```text +Parent virtual pages ──┐ + ├──> shared physical pages +Child virtual pages ───┘ +``` + +在读操作占主导的阶段,两端完全复用同一份内存。一旦任一方首次发起写操作,内存管理单元(MMU)触发 Page Fault,内核捕获缺页中断并分配新的独立物理页,仅为执行写入的一方生成私有副本: + +```text +Before write + +Parent ──┐ + ├──> Page A +Child ───┘ + +After child writes + +Parent ─────> Page A +Child ─────> Page A' +``` + +Copy-on-Write 的本质在于将数据复制的成本后推至物理分歧发生的时刻。状态分叉的初始代价仅包含创建轻量元数据与建立映射拓扑,总体开销由实际修改量界定,与全局状态规模解耦。 + +这一思想被直接引入 Agent 架构。Subagent 能够自父 Agent 的既有会话日志打分叉点,初始阶段引用父级上下文,后续仅追加自身的增量事件: + +```text +Shared conversation prefix + │ + ┌────┴────┐ + ▼ ▼ + Main delta Child delta +``` + +然而,LLM 的上下文并非等价于平坦的只读内存页。父 Agent 的历史会话中可能交织着原始用户指令、中间回复、工具调试输出、鉴权事件及已淘汰的试错路径。全量继承上下文虽然能免去前置的任务提炼,但也会将大量历史噪声、错误先验及冗余 Token 开销无差别倾倒给子任务。 + +因此,Multi-Agent 系统面临的首要架构抉择在于确定上下文分叉的边界与粒度。 + +## Subagent:任务限定的受控工具 + +Maka 在上下文继承上采取了严格的边界策略:Subagent 默认不继承父级会话的完整历史。 + +主 Agent 调度 `agent_spawn` 时,必须提供一份边界封闭、语义自洽的任务规范: + +```text +agent_spawn({ + subagent_id: "local-reader", + task: "检查存储模块如何处理并发写入,并给出文件与符号证据" +}) +``` + +Runtime 据此创建拥有独立会话历史的子实例,并注入角色指令、受限工具集、权限边界与工作区范围。子 Agent 不会获得父级的完整对话;其初始上下文由运行时指令与显式任务规范共同构成,从而将无关的父级历史排除在工作集之外。 + +这要求主 Agent 将全局隐式上下文提炼为可独立执行的契约定义: + +```text +调查 packages/storage 中的并发写入机制。 + +请回答: +1. 哪些对象负责并发控制; +2. 冲突如何被发现; +3. 给出对应文件和符号; +4. 只做只读调查,不修改代码。 +``` + +在主 Agent 与 Subagent 之间,Maka 舍弃了持续性的双向会话交互: + +```text +Main Agent ── task ──> Subagent +Main Agent <── result ── Subagent +``` + +两者之间不存在共享 Mailbox,亦不支持运行期动态磋商指令。主 Agent 专职于顶层目标拆解、算子选型与结果综合,Subagent 聚焦于封闭局部任务的执行。运行时产生的执行事件可投影至前端界面供用户审查,但这属于系统监控层面的单向遥测,不构成 Agent 之间的交互信道。 + +在调用模型视角下,Subagent 遵循工具契约规范:接收结构化任务入参,在分配给它的运行时边界内执行,终态返回结构化状态、摘要文本及产物引用(Artifact Ref): + +```text +result = subagent(role, tools, task, workspace) +``` + +该模式确保了清晰的执行边界,同时也引出了核心架构问题:当子任务之间存在严密的因果依赖,而执行单元之间又缺少动态对话信道时,系统应如何刻画并推进全局执行计划。 + +## DAG:关系引擎的状态演进模型 + +处理具备依赖拓扑的多阶段计算时,有向无环图(Directed Acyclic Graph,DAG)是最为严谨的拓扑表达形式。 + +平坦的线性列表强制执行全序调度(先 A,再 B,后 C)。DAG 则定义了偏序关系:边仅用于约束不可逾越的前置依赖,不存在连接关系的节点可获得天然的并发执行自由度: + +```text +A ───────> C + +B ───────> D +``` + +在此拓扑中,A 构成 C 的前置依赖,B 构成 D 的前置依赖,而 A 与 B 之间互不干扰。调度器无须预先推导串行线性流水线,只需持续识别当前入度已清零(输入依赖已全部就绪)的活跃节点: + +```text +Node = 计算单元(Operator) +Edge = 依赖约束或数据流向 +Ready = 前置输入条件全部达成 +``` + +现代数据库系统早已将意图定义与物理执行严格分层。用户下发声明式 SQL 后,引擎首先构建逻辑执行计划(Logical Plan): + +```text + Aggregate by region + │ + Join + ┌────┴────┐ + Filter Project + │ │ + Scan orders Scan customers +``` + +逻辑计划专职描述关系代数语义。查询优化器可在保障等价语义的前提下,执行谓词下推、列裁剪、Join 重排及表达式折叠。 + +随后,物理计划生成器(Physical Planner)将抽象逻辑算子降级为底层的工程实现: + +```text + FinalHashAggregateExec + │ + RepartitionExec + │ + PartialHashAggregateExec + │ + HashJoinExec + ┌─────┴─────┐ + FilterExec RepartitionExec + │ │ + ParquetScanExec ParquetScanExec +``` + +物理计划负责敲定具体的 Join 算法、分区哈希策略、并发度及跨节点 Exchange 开销。相同的逻辑关系拓扑会依据数据倾斜度、集群拓扑与内存配额,编译出各异的物理计划。 + +物理计划确立后,运行时引擎仍需实例化具体的执行流水线,分配内存池与计算配额,推进批流交互,并统一处理终止、超时、异常与反压信号。 + +能够就地流式处理批数据的算子构成连续流水线: + +```text +Scan ──batch──> Filter ──batch──> Project ──batch──> Sink +``` + +而全局排序、Hash Join 的构建端(Build Side)或全局聚合等算子,由于必须完全吸收前置输入方可输出数据,构成了天然的管线断点(Pipeline Breaker)。执行引擎据此切分物理阶段,精准裁决各个子管线的调度准入与并发水位。 + +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 本身是中间表达(IR),其核心价值在于支撑系统的逐层变换、语义优化、成本评估与底层确定性调度。 + +## Maka Agent Graph:声明式计划与系统驱动推进 + +关系引擎通常能在执行启动前生成封闭确定的物理计划,而 Agent 的认知与执行计划往往高度依赖中间反馈,无法一次性静态穷举。 + +初步的探索性分析可能揭示未知代码分支,局部的实现产物可能彻底改变后续的验证策略,而子任务的执行中断亦会促使规划者切换架构备选方案。Maka 的 Agent Graph 因此被建模为一张在会话生命周期内持续演进的动态 DAG。 + +该机制在职责切分上确立了三权分立: + +- **主 Agent**:专职规划业务蓝图与语义决策。 +- **Coordinator**:专职推进依赖解析与拓扑收敛。 +- **Supervisor**:观察执行图的关键检查点,并在工作流需要语义判断时恢复主 Agent 的决策过程。 + +### 主 Agent 写入 Durable Intent + +在 Maka 中,仅 Root Session 的主 Agent 具备全局执行图的操作权限。主 Agent 负责登记算子定义、派发依赖任务、标记废弃旧节点,并选取收敛产物终结执行图;子会话被剥离了篡改全局拓扑的系统权限。 + +主 Agent 借助 `update_agent_graph` 提交结构化的调度修订(Schedule Revision)。无前置依赖的任务自动并发,后续任务显式锚定上游已落盘的产物记录: + +```text +Runtime review result ─┐ + ├──> Synthesis work +Storage review result ─┘ +``` + +该调用提交的是持久化执行意图(Durable Intent):清晰阐明待追加的工作单元、输入前沿状态、待熔断或替换的历史分支,以及最终采纳的输出集合。 + +每次调度更新均以追加写日志(Append-Only Revision)的形式持久化至 SQLite,并严格打上宿主会话、Run、Turn 与 Call ID 的审计标记。即便主 Agent 进程遭遇崩溃,已提交的执行计划亦绝不会在内存挥发中遗失。 + +### Coordinator 专职状态对齐 + +Coordinator 并不在内存中维护长期易失的权威 DAG 对象。每次执行对齐(Reconciliation)均从持久化存储中重新提取基线事实: + +```text +SQLite control plane + │ + ├── schedule updates + ├── operator provisions + ├── intent claims + └── supervisor wakes + │ + ▼ +Coordinator reconstructs a snapshot +``` + +Coordinator 将追加写的修订事件折叠为当前计划快照,结合已注册的算子定义构建拓扑,再结合各子任务已持久化的 `RuntimeEvent`,计算各节点的终态与就绪边界。 + +```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,而非固定间隔的数据库轮询循环。新的调度提交、子任务事件回传或宿主恢复均可拉起对齐循环;单张执行图在任意时刻仅允许一个 Driver 实例活跃,重复的并发唤醒请求自动合并入后续轮次。 + +### SQLite 担任控制面存储 + +Agent Graph 避免重复造一套平行的 Agent 运行时。SQLite 专职托管全局调度事实,底层实际的模型交互、工具调用、权限审查、异步中断及事件持久化,全量复用成熟的 Session Runtime。 + +```text +Main Agent ──> SQLite schedule + │ + ▼ + Coordinator + │ claim / dispatch + ▼ + Child Sessions / AgentRuns + │ + ▼ + committed RuntimeEvents +``` + +在系统模型中,子会话充当稳定的算子容器,`AgentRun` 对应单次执行生命周期。下游节点消费的是由运行时事件支撑、已经提交的结果记录,并非将每一条 `RuntimeEvent` 都直接视为执行图输入。 + +### Claim 机制解耦就绪计算与执行准入 + +由于 Coordinator 在每次对齐时重建快照,同一个算子节点可能在不同的运算切片中被反复确认为 Ready。若系统在探知就绪时直接触发模型调用,在遭遇重试或瞬时调度抖动时极易产生重复执行。 + +Maka 在实际拉起子任务前,向 SQLite 写入前置条件认领记录(Conditional Claim),将处于 Ready 状态的抽象意图严密绑定至具体算子、会话编号、交互轮次及执行 ID: + +```text +ready intent + │ + ▼ +conditional claim + │ + ├── already exists ──> inspect or recover the same Run + └── new claim ───────> execute the allocated Run +``` + +就绪判定(Readiness)属于可无副作用反复推导的投影视图,而执行准入(Execution Admission)则作为权威事实被一次性原子落盘。 + +### Supervisor 于检查点恢复语义把关 + +确定性的 Coordinator 擅长依据拓扑推进执行,却无法代替大语言模型进行高维语义裁决:评估两份调查结论是否存在逻辑矛盾、判断子任务的执行挫折应采取重试、替换算子亦或彻底推倒既有路线。这些关键决策依赖于主 Agent 的高阶推理。 + +主 Agent 提交完一轮调度规范后,即刻结束当前的监督轮次(Supervisor Turn)。Coordinator 在后台异步调度并发子流水线;一旦全局执行图抵达持久化检查点(Durable Checkpoint),Host 环境唤醒主 Agent 进入全新轮次: + +```text +Main Agent schedules work + │ + ▼ +Coordinator advances Graph + │ + ▼ +durable checkpoint + │ + ▼ +Host wakes Main Agent +``` + +主 Agent 读取有界的执行图快照与下游提交的产物正文,启动下一阶段任务规划、剔除陈旧节点或终结图任务。 + +全流程形成了两种系统能力的互补结合:主 Agent 贡献语义维度的拆解与仲裁,Coordinator 贡献持久化状态机、确定性拓扑演算、并发编排及故障自愈等系统层面的控制保证。 + +## Go Channel:基于通信范式的并发调度 + +DAG 擅长固化静态与动态的宏观依赖,但在微观层面仍需解决任务挂起、事件唤醒与流控反压等调度细节。Go 语言的并发哲学为多智能体调度提供了另一套经典的系统视角。 + +goroutine 是由 Go 运行时自主调度的轻量执行体。经典的 G-M-P 运行时模型将海量的应用协程多路复用至有限的操作系统原生线程:G 代表协程实体,M 对应系统内核线程,P 则抽象了执行 Go 代码所需的逻辑处理器资源。 + +goroutine 实现了极低成本的并发单元实例化,而 Channel 则确立了各单元间的协作契约。 + +### 无缓冲 Channel 作为对齐集合点 + +```go +handoff := make(chan Result) +go func() { handoff <- result }() +received := <-handoff +``` + +无缓冲 Channel 要求发送端与接收端均已就绪方可完成交接。发送端在接收就绪前阻塞,接收端在数据送达前挂起,因此发送与接收操作构成了两个 goroutine 之间的同步点: + +Go 内存模型为 Channel 操作赋予了严谨的 Happens-Before 偏序保障。接收端成功提取数据时,能够安全观察到发送端在投递动作前所完成的全部内存写入。因此,单次 Channel 通信复合了多重职责: + +```text +value transfer + scheduling point + memory ordering +``` + +### 缓冲区界定解耦容限 + +```go +jobs := make(chan Job, 32) +``` + +有缓冲 Channel 允许生产者与消费者在受控深度内异步解耦。只要缓冲区存在未填满槽位,发送操作即可无阻塞完成;一旦缓冲耗尽,发送协程挂起,调度压力沿调用链逆向回溯。 + +容量阈值不仅是性能调优参数,更界定了上游被允许领先下游的最大工作配额。容量过紧会削弱流水线的吞吐平滑度;容量过大则会导致过期任务堆积、内存水位失控,并掩盖下游算子已经发生严重退化的真实瓶颈。 + +### 多路复用与生命周期信令 + +Go 通过 `select` 语法允许单个执行体同时监听多组通信边: + +```go +select { +case job := <-jobs: + return handle(job) +case <-ctx.Done(): + return ctx.Err() +} +``` + +该结构充当声明式的调度接口:执行单元声明其关心的前置信号集,运行时在任一条件达成时将其从等待队列唤醒。 + +`close(ch)` 则是对生命周期终止状态的广播分发。通道关闭后,接收端在排空既有缓冲后,可通过 `value, ok := <-ch` 感知到通道已完结。所有阻塞在空且已关闭通道上的接收者都能观察到这一终态。 + +nil Channel 则具备永不就绪的物理特性。在 `select` 块中动态将某分支的通道变量置为 nil,可在不破坏调度主循环的前提下优雅禁用特定分支,构成高内聚的状态机控制。 + +### 流水线构建中的取消传播 + +多个计算阶段可借助 Channel 串联为处理流水线,亦可通过扇出(Fan-out)与扇入(Fan-in)实现多路并行。然而,下游算子若非正常提前退场,未受保护的上游生产者将永久阻塞于发送点,导致协程泄漏。 + +```go +select { +case out <- result: +case <-ctx.Done(): + return +} +``` + +任何可能比下游消费者存活更久的阻塞通信点,都需要显式的取消路径,使相关 goroutine 能够安全清理并退出。 + +Go Channel 的核心架构特征在于将数据载荷与调度语义高度复合:一次通信行为同时承载了数据交换、依赖表达、状态同步与反压流控: + +```text +communication = dependency + synchronization + backpressure +``` + +这一模型启示了另一种 Multi-Agent 系统设计方案:若为每个 Agent 分配专属收件箱,消息投递行为本身能否直接充当系统调度的核心驱动力。 + +## Codex Subagent:基于私有信箱的协同架构 + +Codex 在其 Subagent 协作演进中给出了另一种实践。它保留了父子任务委派关系,同时将每个 Agent 建模为可在同一根任务树内寻址、持有独立会话历史、依托私有队列异步收发消息的实体。 + +处于同一根任务树中的 Agent 共享一套层级化寻址路径: + +```text +/root +├── /root/runtime_review +├── /root/storage_review +│ └── /root/storage_review/query_analysis +└── /root/test_runner +``` + +该拓扑高度契合经典的 Actor 系统模型: + +```text +Actor identity = AgentPath +Actor state = Thread history +Actor mailbox = Session InputQueue +Actor activation = Turn +``` + +### 信箱作为会话内部的私有队列 + +Codex 核心的 `InputQueue` 结构将数据存储与唤醒信令彻底解耦: + +```rust +struct InputQueue { + activity_tx: watch::Sender, + mailbox_pending_mails: Mutex>, +} +``` + +内存中的 `VecDeque` 在会话存续期间维持消息的先进先出(FIFO)顺序,而 Tokio 的 `watch` 通道则向监听调度器下发信箱发生变动的轻量唤醒脉冲。多条变动信号可以合并,因为待处理负载仍保留在队列中;这个队列本身并不意味着持久化。 + +这并非多个无差别工作节点共同竞争的任务拉取队列。每个会话独占自身的专有信箱,每封通信记录在派发前必须预先绑定发送方(Author)、接收方(Recipient)、文本负载及是否拉起轮次的标志位(`trigger_turn`)。 + +### 消息附带显式调度意图 + +Codex V2 将消息投递清晰划分为两种调度级别: + +```text +send_message = QueueOnly +followup_task = TriggerTurn +``` + +`send_message` 仅执行入队追加。若目标 Agent 正在执行,信件将在后续的模型推理切片边界被统一查阅;若目标 Agent 处于休眠,消息在信箱中静默封存,等待未来的自然交互周期。 + +`followup_task` 则显式注入 `trigger_turn = true` 属性。若目标 Agent 处于空闲等待状态,待办调度器获准为其即时创建新的执行轮次。 + +```text + InterAgentCommunication + │ + ┌──────────┴──────────┐ + │ │ + trigger_turn = false trigger_turn = true + │ │ + queue message wake idle Agent +``` + +单次消息投递同时复合了信息传递、目标寻址与执行调度意图。 + +### 模型采样边界的受控收信机制 + +外部消息绝不会破坏性中断一次正在进行的 LLM 采样请求。新抵达的消息先入队隔离,等待当前交互轮次结束、重新构建下一次模型上下文时批量水合入局: + +```text +Agent B starts sampling + │ +Agent A sends a message + │ + ▼ + B.mailbox.enqueue + │ + current sampling ends + │ + ▼ + drain mailbox + │ + ▼ +build next model request +``` + +Codex 引入了 `MailboxDeliveryPhase` 状态机。轮次启动初期,信箱积压邮件被允许注入上下文;一旦运行时已在本地生成并记录了面向用户的最终输出,迟到的后发消息将被强制顺延至后续轮次,严防已敲定的结论被并发涌入的次级消息无序篡改。 + +### 任务完结作为结构化消息回传 + +Codex 为子任务配置了专属的状态观察者(Completion Watcher)。子任务推进至最终稳态后,观察者构造一条由子任务发往父任务的通信记录,并安全置入父任务信箱。 + +此完结信件的 `trigger_turn` 默认配置为 `false`。执行结果首先作为一条事实沉淀入父级收件箱,避免非预期打断父任务可能正在专注展开的上下文。 + +`wait_agent` 工具通过等待父会话的信箱活动来实施流控,而不是直接返回某个子任务的结果: + +```text +wait_agent + │ + ├── new mail ─────> wake + ├── user steer ───> interrupt wait + └── deadline ─────> timeout +``` + +该工具仅负责执行挂起与唤醒协议;实际的结果内容仍保留于信箱底层,在随后的正常轮次中并入模型上下文。 + +### 对话交织中演进的工作流 + +基于 DAG 的体系将所有依赖固化为显式拓扑边,而基于 Mailbox 的体系则呈现为一系列在时间线上展开的动态消息流: + +```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 亦能在子任务运行中途随时追加补充指引。真实的业务工作流无须在初始阶段完成全量预测,而是在多方对话交互中逐步成型。 + +动态灵活性对应着系统复杂度的转移:系统控制流被分散掩埋在跨 Agent 的通信历史中。追溯某个决策变更的原因需要完整重放对应信箱的吸收序列;研判某个中间节点是否已满足执行条件,亦无法简单依赖全局有向图的拓扑入度来决断。 + +Codex 架构的技术本质可概括为:在根任务范围内保留父子委派关系,并在其上叠加 Actor 风格的私有信箱协作层。 + +## 架构选型:工作流编排与消息协同 + +Maka 与 Codex 均支持 Subagent 派生、并发执行及追加任务,但在系统底层原语的设计上做出了截然不同的取舍。 + +| 维度 | Maka 工作流模型 | Codex 信箱协作模型 | +| ------------ | ---------------------------------------- | ------------------------------------------ | +| **核心抽象** | DAG 拓扑中的算子与数据边 | 可在根任务树内寻址的 Agent 与私有信箱 | +| **任务派发** | 主 Agent 显式编写调度计划 | 父级派发或对等实体投递 Follow-up | +| **调度条件** | Coordinator 演算节点依赖就绪度 | 消息入队、`trigger_turn` 标记及活跃度 | +| **数据传递** | 结构化记录沿依赖边注入下游 | 消息体在模型轮次边界并入会话上下文 | +| **横向交互** | 不向子节点暴露直接通信通道 | 根任务树内的 Agent 支持直接消息传递 | +| **全局视图** | 随时可提取全局确定性图快照 | 需汇聚各 Agent 状态与信箱积压综合反推 | +| **核心优势** | 显式拓扑、确定性执行、原生易于审计与恢复 | 具备极高弹性、支持动态协商、适配开放式探索 | +| **核心代价** | 动态调整需重新提请修改调度图 | 控制流隐式离散,消息与上下文极易膨胀 | + +工作流模型适用于前后依赖清晰、产物结构规范、单次执行跨度较长且对崩溃恢复要求极高的系统级工程任务。静态代码分析、并行构建测试、大规模数据管道及多阶段技术调研均具备明确的“算子与产物”特征。 + +信箱协同模型更适用于行动分支高度取决于即时语义发现、各角色需密集双向推演、且全流程无法预先穷举的探索性任务。系统方案研讨、交叉代码审查与发散式安全渗透更贴合此类去中心化的交互结构。 + +两种路径的底层分水岭在于协调状态(Coordination State)的物理托管位置: + +- **Maka**:将协同状态上浮收拢于全局执行图(Coordination lives in the Graph)。 +- **Codex**:将协同状态离散下沉于多方通信流(Coordination lives in the Conversation)。 + +Agent Graph 将计划从大模型的易失上下文中抽离,交由持久、可重放的系统协调器推进;Mailbox 机制则赋予智能体更自由的动态调整空间。前者更贴近确定性的关系数据库计算引擎,后者则深植于经典的 Actor 并发框架。 + +这也阐释了 Maka 约束 Subagent 横向直接交流的设计取向:将协同过程静态编译为透明受控的调度图。大模型专职贡献高价值的语义逻辑判断,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)