Workflows

Multi-Agent Coordination

How multiple AI agents coordinate via LeanCTX: registry, message bus, task delegation, shared cache, persistent diaries, and context handoff.

LeanCTX enables multiple AI agents (Cursor, Claude Code, Codex, Gemini CLI, Pi, etc.) to work on the same project simultaneously with coordination, not conflict. The multi-agent system is built on 5 primitives: registry, message bus, task delegation, shared cache, and persistent diaries.

Agent Registry

Each agent registers when it joins a session, declaring its type and role. This enables targeted communication and role-based task assignment.

ctx_agent action="register" agent_type="cursor" role="dev"
→ Registered: cursor-dev-1 (agent_id: a1b2c3)

ctx_agent action="list"
→ 3 agents active:
  a1b2c3  cursor  dev     active  2 min ago
  d4e5f6  claude  review  active  5 min ago
  g7h8i9  codex   test    idle    12 min ago

The wake-up briefing from ctx_overview reads the same registry, but prunes stale and foreign agents (#419): peers from crashed or exited MCP processes are dropped via cleanup_stale, and the list is scoped to the current project root — so it shows only the agents genuinely live in this project, matching what ctx_agent list and the dashboard already report.

Agent Types

TypeDescription
cursorCursor IDE agent
claudeClaude Code / Claude Desktop
codexOpenAI Codex CLI
geminiGemini CLI
crushCrush IDE agent
subagentSpawned sub-agent

These are common examples — agent_type is a free-form label and lean-ctx supports 30+ agent types across every detected editor and CLI, so any configured agent participates in the same registry, scent field and message bus.

Roles

RoleTypical Responsibility
devPrimary development - writing code
reviewCode review - checking quality, security, patterns
testTesting - writing and running tests
planArchitecture - planning and design decisions

Stigmergic Scent Field

Direct messages cost tokens; most coordination doesn't need them. Agents also coordinate indirectly through a shared scent field — deposits of CLAIMED, DONE, STUCK, HOT and AVOID on files and tasks that decay exponentially (10–60 min half-life). Claims prevent duplicate work with zero coordination overhead until the moment of conflict:

ctx_agent action="claim" message="src/auth/session.rs"
→ Claimed: src/auth/session.rs (decays in ~10m unless re-claimed)

# Second agent, same file:
→ Error: already claimed by local-48121 (2m ago, still active)

ctx_agent action="release" message="src/auth/session.rs"
→ Released: src/auth/session.rs

ctx_read surfaces foreign claims inline ([scent: claimed by …]), bounces and failed edits auto-deposit STUCK, and ctx_agent sync renders the live field. Unconfigured processes get PID-distinct identities, so two editor windows on one machine genuinely see each other. See Adaptive Learning for the full learning-layer picture.

Message Bus

Agents communicate via broadcast or direct messages, categorized for filtering:

# Broadcast a finding to all agents
ctx_agent action="post" category="finding" message="Auth token expiry is hardcoded at 3600s"

# Send directly to the reviewer
ctx_agent action="post" to_agent="d4e5f6" category="request" message="Please review auth.ts changes"

# Read pending messages
ctx_agent action="read"
→ 2 new messages:
  [finding] from cursor-dev-1: Auth token expiry is hardcoded at 3600s
  [status] from codex-test: All 42 tests passing

Selective Routing

Instead of full broadcast, agents subscribe with TopicFilters to receive only relevant events. Filters support event kind, actor, consistency level, and agent identity. Directed events target specific agents, reducing noise and saving tokens.

# Poll events with filtering (via ctx_agent)
ctx_agent action="poll_events" category="default" message="0"
→ Events (3, since=0):
  #1 [session_mutated] actor=cursor-dev cl=strong (14:30:12)
  #2 [knowledge_remembered] actor=codex-test cl=eventual (14:31:05)
cursor=2

Remote Agent Bus

The Remote Agent Bus extends the local message bus over HTTP, enabling agents on different machines or in different processes to coordinate. Agents register, maintain heartbeats, and receive events through a standard REST + SSE interface.

Endpoints

MethodEndpointDescription
POST/v1/agents/registerRegister an agent (body: agent_type, role, project_root)
POST/v1/agents/heartbeatSend heartbeat to keep registration alive (body: agent_id)
GET/v1/agents/listList all active agents with status and metadata
POST/v1/agents/deregisterDeregister an agent (body: agent_id)
GET/v1/agents/eventsSSE stream of agent registry changes (join, leave, heartbeat)
# Register a remote agent
POST /v1/agents/register
{
  "agent_type": "claude",
  "role": "review",
  "project_root": "/home/user/my-project"
}
→ { "agent_id": "d4e5f6", "status": "active" }

# Keep registration alive
POST /v1/agents/heartbeat
{ "agent_id": "d4e5f6" }
→ { "ok": true, "ttl_seconds": 60 }

# Subscribe to agent events (SSE)
GET /v1/agents/events
→ event: agent_joined
  data: {"agent_id":"d4e5f6","agent_type":"claude","role":"review"}

→ event: agent_left
  data: {"agent_id":"a1b2c3","reason":"heartbeat_timeout"}

Agents that miss 3 consecutive heartbeats are automatically deregistered and an agent_left event is emitted on the SSE stream.

Per-Agent Context Ledger

Each agent gets its own isolated context ledger, preventing cross-contamination between agents working in the same project. Ledgers live in the XDG state dir at $XDG_STATE_HOME/lean-ctx/ledger/{<agent_id>}.json (legacy single-dir installs: ~/.local/state/lean-ctx/ledger/). The default (non-multi-agent) session uses context_ledger.json.

  • Ledger tracks file reads, cache entries, knowledge mutations, and tool call history per agent
  • Agents cannot read or modify another agent's ledger
  • Ledger files are created on first registration and cleaned up on session end
  • The handoff system merges ledger state when transferring context between agents
# Ledger storage layout (XDG state dir)
$XDG_STATE_HOME/lean-ctx/ledger/
├── context_ledger.json      # default agent (single-agent mode)
├── a1b2c3.json              # cursor-dev-1 ledger
├── d4e5f6.json              # claude-review ledger
└── g7h8i9.json              # codex-test ledger

Agent Token Budget

Configurable per-agent token limits prevent runaway context consumption. When an agent exceeds its budget, further reads are blocked until the budget is reset or increased. Warnings are emitted at >80% usage.

Configuration

# config.toml — per-agent token budget (0 = unlimited, the default)
agent_token_budget = 500000

Behavior

UsageEffect
<80%Normal operation
≥80%Warning injected into tool responses
100%Reads blocked — agent receives error with current usage stats
# Budget warning in tool response
⚠ Token budget: 412,000 / 500,000 (82.4%) — consider archiving or compacting

# Budget exceeded
✗ Token budget exceeded: 500,000 / 500,000 (100%)
  Use ctx_compress to reclaim context or ctx_session action=budget to raise the limit

A2A Transport

TransportEnvelopeV1 enables cross-machine agent handoffs. Context packages (using the open .ctxpkg format) and handoff bundles are wrapped with agent identity and HMAC-SHA256 signatures for integrity.

# Send a context package to a remote daemon
lean-ctx pack send myproject.ctxpkg --target=http://remote:3344 --secret=mykey

# Receive and verify on the other end
lean-ctx pack receive envelope.json --secret=mykey --apply

HTTP endpoint: POST /v1/a2a/handoff accepts TransportEnvelopes directly. Agent discovery: GET /.well-known/agent.json returns the A2A v1.0 Agent Card.

Google A2A Compatibility

LeanCTX exposes a JSON-RPC 2.0 endpoint at /a2a compatible with the Google A2A Protocol. Supported operations: tasks/send, tasks/get, tasks/cancel.

# JSON-RPC request to create a task
POST /a2a
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tasks/send",
  "params": {
    "to": "lean-ctx",
    "message": {
      "role": "user",
      "parts": [{"type": "text", "text": "Fix the auth bug"}]
    }
  }
}

Task Delegation

ctx_task enables structured task assignment with tracking:

# Create and assign a task
ctx_task action="create" description="Write tests for auth token refresh" to_agent="g7h8i9"
→ Task T1 created, assigned to codex-test

# Check task status
ctx_task action="list"
→ T1: "Write tests for auth token refresh" → codex-test [in_progress]
   T2: "Review security of token storage" → claude-review [pending]

Shared Session Cache

All agents in a session share the same underlying file cache: content, hashes and the compressed form are computed once and reused, so a second agent never pays for a redundant disk read or re-compression. What is not shared is the cheap ~13 token "unchanged" stub — that is conversation-scoped (v3.8.14), because it asserts "you already have this in your context", which is only true for the agent that actually received the full content.

  • Agent A reads auth.ts → full content, cached as F1 (hash + compressed form shared)
  • Agent B reads auth.ts → warm cache, no disk read — but receives the full content the first time, since B never had it in context
  • Agent B re-reads auth.ts unchanged → now gets the ~13-token stub (B's own re-read)
  • Agent A edits auth.ts → cache auto-invalidated for everyone; the next read returns fresh content with the updated hash

A spawned subagent (Task) is scoped the same way under its own task id — it reuses cheap stubs for its own consecutive reads but is never served a stub for a file only its parent read. See Caching → Re-read Reliability.

Persistent Diaries

Each agent can maintain a diary that persists across sessions. Diary entries are categorized for later retrieval:

# Log a discovery
ctx_agent action="diary" category="discovery" message="Config uses TOML, not YAML as documented"

# Log a decision
ctx_agent action="diary" category="decision" message="Using JWT RS256 for auth tokens"

# Log a blocker
ctx_agent action="diary" category="blocker" message="Cannot test refresh flow - mock server needed"

# Recall diary in a new session
ctx_agent action="recall_diary"
→ 3 entries from cursor-dev-1:
  [discovery] Config uses TOML, not YAML as documented
  [decision] Using JWT RS256 for auth tokens
  [blocker] Cannot test refresh flow - mock server needed

Context Handoff

ctx_handoff transfers the full working context between agents or sessions. This includes cached files, knowledge base entries, workflow state, and session context.

# Agent A creates a handoff bundle before ending
ctx_handoff action="create"
→ Handoff created: 12 files, 3 knowledge entries, 1 active workflow

# Agent B (or a new session) imports it
ctx_handoff action="import" apply_knowledge=true apply_session=true apply_workflow=true
→ Imported: 12 cached files (F1-F12), 3 knowledge entries, workflow "bugfix" at step "test"
  Ready to continue where Agent A left off.

Signed Handoff Bundles

Handoff bundles (HandoffTransferBundleV1) are cryptographically signed with Ed25519 to ensure integrity and authenticity. Each agent has its own keypair stored in the data dir at $XDG_DATA_HOME/lean-ctx/keys/ (legacy: ~/.lean-ctx/keys/), generated automatically on first registration.

Key Storage

$XDG_DATA_HOME/lean-ctx/keys/
├── a1b2c3.pub              # cursor-dev-1 public key
├── a1b2c3.key              # cursor-dev-1 private key (600 permissions)
├── d4e5f6.pub              # claude-review public key
└── d4e5f6.key              # claude-review private key

Signing & Verification

  • sign_bundle() — signs the serialized bundle with the agent's private key, appending the signature to the bundle envelope
  • verify_bundle_signature() — verifies the signature against the sender's public key before importing
  • Import rejects bundles with invalid or missing signatures — no unsigned handoffs are accepted
  • Signatures cover the full bundle payload (cached files, knowledge, workflow state, session context)
# Saving a handoff (automatically signed)
ctx_handoff action="save" apply_knowledge=true apply_session=true
→ Context saved and signed: 12 files, 3 knowledge entries
  Signature: Ed25519(a1b2c3) ✓

# Loading a handoff (signature verified before import)
ctx_handoff action="load"
→ Signature verified: Ed25519(a1b2c3) ✓
  Restored: 12 cached files, 3 knowledge entries

# Rejected handoff (tampered bundle)
ctx_handoff action="load"
→ ✗ Signature verification failed — bundle rejected
  Expected signer: a1b2c3, got: invalid signature

Compaction Survival

When AI providers truncate or compact the context window (e.g., Claude's context window management, Cursor's automatic compaction), LeanCTX sessions can lose critical state. The Compaction Survival system detects and recovers from these events automatically.

PreCompact Hook

The PreCompact hook fires when LeanCTX detects an imminent compaction event, giving the system a chance to save critical state before the context is truncated:

  • Saves current session state (cached files, file references, active tasks)
  • Creates a compact resume block (≤500 tokens) summarizing everything needed to continue
  • Writes the resume block to $XDG_DATA_HOME/lean-ctx/sessions/<id>/resume.json

Heuristic Detection

LeanCTX detects compaction even without an explicit hook by monitoring for a tool-call counter mismatch:

  1. LeanCTX tracks how many tool calls have been made in the session (server-side counter)
  2. The model's context includes an expected counter value from the session banner
  3. After compaction, the model may "forget" recent calls - the next tool call has a counter gap
  4. When a gap is detected, LeanCTX automatically injects the resume block into the response
# Normal flow: counters match
Tool call #15 → server expects #15 ✓

# After compaction: model "forgot" calls 10-14
Tool call #10 → server expects #15 ✗ (gap detected)
→ Auto-injecting resume block...

Resume Block

The resume block is a compact summary (max 500 tokens) that restores critical session context:

ctx_session action="resume"
→ Session resumed from checkpoint:
  Files: F1=auth.ts F2=server.rs F3=db.ts (3 cached)
  Task: "Fix JWT refresh race condition" (step 3/5)
  Archives: [a7f3c2] auth analysis, [b8d4e1] test results
  Diary: 2 entries (1 decision, 1 discovery)
  Agent: cursor-dev-1, role: dev

Archive Interaction

The resume block includes references to all active ctx_expand archive IDs. This means that even after compaction, the agent can still retrieve large tool results that were archived before the context was truncated:

  • Archive IDs listed in resume block are validated - expired entries are excluded
  • The agent can call ctx_expand id="..." to retrieve any listed archive
  • If the archive TTL expired during compaction, the resume block notes it as [expired]

Configuration

# config.toml
[compaction]
resume_max_tokens = 500       # Max size of the resume block
auto_detect = true            # Enable heuristic detection
counter_gap_threshold = 2     # Min gap to trigger resume injection

Best Practices

  • Register early: Call ctx_agent action="register" at the start of every multi-agent session
  • Use diaries: Log important discoveries and decisions - they persist and help future sessions
  • Categorize messages: Use finding, warning, request, status for easy filtering
  • Delegate with context: Include clear descriptions when assigning tasks
  • Track costs: Use ctx_cost action="agent" to monitor per-agent token spend