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
| Type | Description |
|---|---|
cursor | Cursor IDE agent |
claude | Claude Code / Claude Desktop |
codex | OpenAI Codex CLI |
gemini | Gemini CLI |
crush | Crush IDE agent |
subagent | Spawned 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
| Role | Typical Responsibility |
|---|---|
dev | Primary development - writing code |
review | Code review - checking quality, security, patterns |
test | Testing - writing and running tests |
plan | Architecture - 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
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/agents/register | Register an agent (body: agent_type, role, project_root) |
POST | /v1/agents/heartbeat | Send heartbeat to keep registration alive (body: agent_id) |
GET | /v1/agents/list | List all active agents with status and metadata |
POST | /v1/agents/deregister | Deregister an agent (body: agent_id) |
GET | /v1/agents/events | SSE 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
| Usage | Effect |
|---|---|
<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.tsunchanged → 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 envelopeverify_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:
- LeanCTX tracks how many tool calls have been made in the session (server-side counter)
- The model's context includes an expected counter value from the session banner
- After compaction, the model may "forget" recent calls - the next tool call has a counter gap
- 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,statusfor easy filtering - Delegate with context: Include clear descriptions when assigning tasks
- Track costs: Use
ctx_cost action="agent"to monitor per-agent token spend