A pattern for multi-agent coordination where safety constraints are enforced at the database layer, not in LLM prompts. From fibon design log Chapter 2.
When one LLM handles too many concurrent tasks, three cognitive defects emerge:
User → Butler (meta-decision, memory keeper, reasoning model)
├── Research Assistant (divergent thinking, cheap model)
├── Code Assistant (convergent thinking, cheap model)
├── Scheduling Assistant (structured output, cheap model)
└── Project Manager (analytical thinking, cheap model)
Butler — sole entry point, handles meta-decisions (should I delegate? to whom? should I ask the user?), keeps long-term memory, uses reasoning model (e.g. Claude Sonnet with extended thinking).
Assistants — domain-specific workers, receive filtered task descriptions (not full conversation history), use cheaper models. Each has a distinct "cognitive style" to prevent personality collapse.
| Style | Behavior | Use Case |
|---|---|---|
integrative |
List trade-offs, then synthesize | Butler default |
divergent |
Open-ended, ≥3 angles | Research analysis |
convergent |
Minimal repro, code-level precision | Code assistant |
structured |
Table/JSON output, explicit schema | Scheduling |
analytical |
Break into ≤5 verifiable subtasks | Project manager |
The critical insight: safety constraints must live where the LLM cannot see or modify them. Prompt-level rules fail because:
MAX_GLOBAL_SPAWN_DEPTH)agent_spawn_records before each spawndelegation_rounds table via COUNT(*) checkUPDATE agent_spawn_records
SET ping_pong_count = ping_pong_count + 1
WHERE ((parent_agent_key = %s AND child_agent_key = %s)
OR (parent_agent_key = %s AND child_agent_key = %s))
AND ping_pong_count < max_ping_pong_turns
AND status = 'active'
RETURNING ping_pong_count, max_ping_pong_turns
If 0 rows returned → brake triggered, message blocked. Atomic: no race condition possible.
| Brake | Enforcement | Hardness |
|---|---|---|
| Ping-pong | Atomic SQL UPDATE (check + increment in one op) | Hardest |
| Depth & Concurrency | SELECT → Python check → INSERT (tiny race window) | Hard (known trade-off) |
When delegation rounds exceeded:
delegate_to_assistant returns [escalation] context to Butler (round history + "please handle yourself")delegation_rounds table + max_rounds_exceeded metric| Approach | Pros | Cons | Decision |
|---|---|---|---|
| Redis atomic counter (INCR/Lua) | Fast | Volatile, audit trail needs separate work | Rejected |
| Pessimistic lock (SELECT FOR UPDATE) | Correct | Longer lock hold, extra round-trip | Overkill |
| Serial queue (Actor model) | No concurrency at source | Extra dependency, fault point | Overkill for low-frequency spawns |
| PostgreSQL atomic UPDATE | Durable, auditable, already in stack | Slightly slower than Redis | Chosen |
The design question reduces to: where does the arbitration point live? fibon chose the database because it provides persistence + audit + is already a dependency.
安全防護不能寄託在拜託 LLM「自己適可而止」的軟性文字上。 Safety cannot depend on politely asking the LLM to self-limit.
The asymmetry is clear: building database-level brakes costs ~half a day of work; the protection it provides is permanent regardless of how badly the LLM is tricked upstream.