Skip to main content
The AgentBus runs multiple AI agents in parallel without duplicate work, file conflicts, or lost findings.

Architecture

+----------+  +----------+  +----------+
|  Explore |  |   Code   |  | WebSearch|
|  Agent   |  |  Agent   |  |  Agent   |
+----+-----+  +----+-----+  +----+-----+
     |             |              |
     +--------+----+--------------+
              v
     +----------------+
     |    AgentBus    |
     |                |
     |  File Cache    |  <- shared reads, no duplicates
     |  Tool Cache    |  <- LRU 200, cross-agent reuse
     |  Edit Mutex    |  <- serialized writes per file
     |  Findings      |  <- real-time peer communication
     |  Generation    |  <- freshness tracking
     +----------------+

File cache

Read deduplication

When agent A reads src/index.ts, the content is cached. When agent B reads the same file:
Agent A: acquireFileRead("src/index.ts")
  -> { cached: false, gen: 0 }  // A is first, starts reading

Agent B: acquireFileRead("src/index.ts")
  -> { cached: "waiting", content: Promise<string> }  // B waits for A

Agent A finishes: releaseFileRead("src/index.ts", content)
  -> B's Promise resolves with same content
One disk read serves both agents.

Generation counter

Each cache entry has a gen counter, incremented on every edit. Agents can check whether content has been updated by another agent since their last read.

Invalidation

invalidateFile(path):
  1. Resolve any waiting agents with null (so they re-read from disk)
  2. Clear the cache entry
  3. Expire any tool result cache entries that reference this path

Tool result cache

LRU cache with 200 entry limit. Keyed by toolName:canonicalized-args:
grep:handleSubmit:src/components:*.tsx
read_code:AgentBus:src/core/agents/agent-bus.ts
glob:**/*.test.ts:src/
Cached tools: read_code, grep, glob, navigate, analyze, web_search.

Cross-dispatch persistence

// First dispatch:
const bus1 = new AgentBus(sharedCache);  // cold start
// ... agents run ...
sharedCache = bus1.exportCaches();       // export warm cache

// Second dispatch:
const bus2 = new AgentBus(sharedCache);  // starts warm
// Agent reads "src/index.ts" -> cache hit from previous dispatch
exportCaches() returns only completed file reads and the full tool result cache. Pending reads and edit state are not exported.

Edit mutex

Concurrent edits to the same file are serialized via promise chaining:
Agent A: edit "src/index.ts" -> lock acquired, edit proceeds
Agent B: edit "src/index.ts" -> queued behind A's Promise
Agent A: edit complete -> B's edit proceeds
The first editor becomes the “owner”. If a second agent edits the same file, it receives a warning. After all dispatches complete, the parent agent is told about conflicts.

Findings

Real-time peer communication without shared context windows.
1

Report

Agent calls report_finding("auth pattern", "Uses JWT with refresh tokens stored in HttpOnly cookies")
2

Store

Finding appended to bus with agentId, label, content, timestamp. Deduplication via findingKeys set.
3

Track

Each agent tracks _lastSeenFindingIdx — its index into the findings array.
4

Drain

On each step, prepareStep() calls drainUnseenFindings(agentId) — returns findings since last drain.
5

Inject

New findings injected into the system prompt as peer discovery messages.
Findings propagate within one step (~100ms agent stagger + step processing time). One agent’s discovery influences the next agent’s tool calls within 1-2 steps.

Dispatch orchestration

Multi-agent dispatch

Forge calls dispatch([
  { task: "Find all auth middleware", agent: "explore" },
  { task: "Add rate limiting to /api/users", agent: "code" }
])
  1. Create AgentBus (import previous SharedCache if available)
  2. Register tasks on the bus
  3. Spawn agents with 100ms stagger (prevents thundering herd on file reads)
  4. Each agent runs independently with shared bus access
  5. Wait for all agents to complete (or hit timeout/budget)
  6. Aggregate results: each agent’s structured output + bus findings
  7. Apply toModelOutput() compression
  8. Export caches for next dispatch
  9. Return combined result to Forge

Single-agent optimization

If only one task is dispatched, the system skips bus coordination overhead and returns the result directly.

Result compression

toModelOutput() compresses the dispatch result before the parent Forge sees it:
  • Collapse consecutive blank lines
  • Truncate individual lines (500 char limit, skip inside code blocks)
  • Strip verbose reasoning that doesn’t add information
See also: User steering for redirecting agents mid-execution.