#Overview
A single router process manages all standing agent turn loops. No separate terminal per agent. Agents subscribe to rooms; the user directs attention by choosing a room. The router handles scheduling, priority, and message delivery.
#Router
One process. Manages all standing agents. Each agent has three priority queues: urgent, normal, background.
Selection algorithm (per agent, after each turn):
- If urgent queue non-empty → take from urgent. Always.
- Otherwise: maintain a credit counter (starts at 3). Each normal turn costs 1 credit. At 0 credits, take one background turn and reset to 3. If selected queue is empty, fall through to the other.
- Result: 3:1 normal:background ratio when no urgent messages exist.
Aging (prevents starvation):
- Background message waiting > 10 turns → promote to normal
- Normal message waiting > 20 turns → promote to urgent
#Priority
Three levels: 1=urgent, 2=normal, 3=background.
Default: user messages are urgent. Agent-to-agent messages are normal unless explicitly set.
Inference (rules-based, at router intake):
- Contains "urgent", "blocked", "critical", "stop" → urgent
- Direct question to a specific agent → normal
- Broadcast, FYI, convention update → background
Explicit override: sender sets priority field on the message. Agent-to-agent: if sender is blocked waiting, mark urgent. If a side thought, mark normal.
#Rooms
A room is a named channel with one or more agent subscribers. Agents can be in multiple rooms simultaneously. User chooses which room to read and write in.
Turn routing: user message to a room → router delivers to all subscribed agents, highest-priority agent responds first. Agent-to-agent message in a room → visible to all room members including user.
Cross-room: messages between agents in different rooms go via mailbox, delivered at next turn.
#Event format — finalized (Method 49 alignment)
Every event the router emits over WebSocket is a JSON object. The GUI renders these; the sidecar archives them.
#Common fields (all events)
| Field | Type | Description | ||||
|---|---|---|---|---|---|---|
id | string | Event identifier — uuid4() now; SHA256 of content in Filix (see Forward declarations below) | ||||
sig | string | Agent signature — "" now; cryptographic in Filix (see Forward declarations below) | ||||
type | "dialogue" \ | "tool" \ | "mailbox" \ | "pass" \ | "system" | Event kind |
room | string | Room name | ||||
from | string | Source — agent name, "user", or "router" (for system events) | ||||
priority | "urgent" \ | "normal" \ | "background" | Message priority as string | ||
ts | string | ISO 8601 timestamp | ||||
turn | int | Turn number within the agent's session |
#dialogue — agent response text
Streaming semantics: during generation, the router emits events with a chunk field containing each text fragment. On completion, it emits a final event with done: true and the full content. The GUI appends chunks for live display, then replaces with final content on done.
{
"id": "a1b2c3d4-...",
"sig": "",
"type": "dialogue",
"room": "reed",
"from": "Reed",
"priority": "normal",
"ts": "2026-03-17T18:30:00Z",
"turn": 14,
"chunk": "Backup sync is"
}
{
"id": "a1b2c3d4-...",
"sig": "",
"type": "dialogue",
"room": "reed",
"from": "Reed",
"priority": "normal",
"ts": "2026-03-17T18:30:00Z",
"turn": 14,
"done": true,
"content": "Backup sync is running. Three DBs verified."
}
| Field | Type | Present | Description |
|---|---|---|---|
chunk | string | During streaming | Text fragment for live display |
done | bool | On completion | true signals final event |
content | string | On completion | Full response text |
Renders as: plain text chat bubble. Never collapsed. Never indexed.
#tool — subagent spawn or tool call
Two layers of output: the raw result (what the tool returned) and the distillation stub (what stays in the agent's context). The router writes the stub AFTER the tool returns — this is Method 49's distillation step. result goes to the sidecar as S{ix}. stub is what the agent keeps.
{
"id": "e5f6g7h8-...",
"sig": "",
"type": "tool",
"room": "reed",
"from": "Reed",
"priority": "normal",
"ts": "2026-03-17T18:30:05Z",
"turn": 14,
"ix": 3,
"tool_name": "Agent",
"args": {"task": "audit backup state for all local DBs"},
"label": "backup-audit",
"status": "pending"
}
{
"id": "e5f6g7h8-...",
"sig": "",
"type": "tool",
"room": "reed",
"from": "Reed",
"priority": "normal",
"ts": "2026-03-17T18:30:47Z",
"turn": 14,
"ix": 3,
"tool_name": "Agent",
"args": {"task": "audit backup state for all local DBs"},
"result": "Full 800-token subagent output...",
"label": "backup-audit",
"stub": "3 DBs synced, entities.db 12h stale, no S3 copy",
"status": "done",
"duration_ms": 42000
}
| Field | Type | Description | |||
|---|---|---|---|---|---|
ix | int | §N index — monotonic per room, assigned to every non-dialogue event | |||
tool_name | string | Tool invoked: Agent, Read, Write, Bash, etc. | |||
args | object | Arguments passed to the tool call | |||
result | string \ | null | Raw tool output. Archived to sidecar as S{ix}. Set on completion. | ||
label | string | 2-4 word name for the expander header | |||
stub | string \ | null | One-line distillation — what the agent learned. Written by the router AFTER the tool returns (Method 49). This is what stays in the agent's context. | ||
status | "pending" \ | "running" \ | "done" \ | "error" | Lifecycle. GUI shows spinner for pending/running. |
duration_ms | int \ | null | Elapsed time. Set on completion. |
Lifecycle: The router emits the event twice — once at status: "pending" (with tool_name and args, no result/stub yet), once at status: "done" (with result, stub, duration). The GUI updates in place using ix as the key. On error, result contains the error message.
Sidecar mapping: result is appended to data/context-sidecar-{session-id}.md as section S{ix}. The agent's context retains only stub. This is Method 49's core mechanism — the event format and the distillation format are the same structure.
#mailbox — injected dispatch message
{
"id": "i9j0k1l2-...",
"sig": "",
"type": "mailbox",
"room": "reed",
"from": "Veda",
"priority": "normal",
"ts": "2026-03-17T18:31:00Z",
"turn": 14,
"ix": 4,
"subject": "entities.db has no verified backup",
"body": "Full dispatch message body...",
"mailbox_id": 305
}
| Field | Type | Description |
|---|---|---|
ix | int | Same §N counter as tool events — shared sequence |
subject | string | Dispatch subject line — serves as both label and stub |
body | string | Full message body. Archived to sidecar. GUI shows on expand. |
mailbox_id | int | Reference to agent-mailbox.jsonl entry |
Renders as: collapsible expander, teal accent. Collapsed shows from + subject. Same sidecar treatment as tool events.
#pass — agent chose silence
{
"id": "m3n4o5p6-...",
"sig": "",
"type": "pass",
"room": "reed",
"from": "Veda",
"priority": "normal",
"ts": "2026-03-17T18:30:02Z",
"turn": 7
}
Not written to the persistent room log. The GUI shows a brief fade animation at the agent's position — acknowledges the agent was queried without cluttering the transcript. An agent emits <PASS> as its entire response; the router converts this to a pass event.
#system — router-generated notification
{
"id": "q7r8s9t0-...",
"sig": "",
"type": "system",
"room": "reed",
"from": "router",
"priority": "background",
"ts": "2026-03-17T18:29:00Z",
"turn": 0,
"content": "Veda joined the room"
}
Router-generated events for structural changes: agent joined, agent left, room created, agent restarted. from is always "router". turn is 0 (system events are not agent turns). Rendered in the GUI as a muted inline notification.
#§N references in dialogue
When an agent's dialogue references evidence, it uses §N:
> "Backups are sparse §3 and I've flagged it to Via §4."
The GUI can optionally hyperlink these to the corresponding expander. The sidecar can be retrieved by section number.
#Index counter rules
ixis per-room, monotonic, starts at 1 for each room's lifetime.- Only
toolandmailboxevents get anix.dialogue,pass, andsystemevents do not. - The router assigns
ix, not the agent. Agents reference §N in their text; the router resolves which N maps to which event. - On agent restart (new session in same room),
ixcontinues from where it left off. The counter belongs to the room, not the session.
#Forward declarations — Filix/Rust
These fields are present in all events now but unused by the Python router. The Filix v1.0 Rust runtime fills them in.
id — event identifier. Currently uuid4(). In Filix: SHA256 of event content (type + room + from + ts + content fields). Makes the event log content-addressed. Events chain into a Merkle tree — the room's history becomes a verifiable ledger.
sig — agent signature. Currently empty string "". In Filix: the sending agent signs id with their private key (managed by Dwara, Architecture: Keymaster). Receivers verify against the agent's public key in the identity graph (data/entities.db). Cryptographic attestation of who said what, when.
No schema change required when Filix arrives. The Python router ignores both fields. The Rust runtime populates them. Verifiability is additive — the protocol is the same, the trust level upgrades.
#GUI (to be built by Rune)
Room list (sidebar):
- Rooms sorted by urgency of pending messages
- Urgent rooms marked
!(Materialpriority_highicon) - Background-only rooms marked
·(Materiallow_priority) - Normal/no pending: no indicator
- Unread count shown per room
Conversation view:
dialogueevents: plain text, inlinetoolevents: collapsible expander, neutral style. Collapsed = stub label. Expanded = full output.mailboxevents: collapsible expander, distinct color (teal). Collapsed = sender + subject. Expanded = full body.- Each expander is an index point (§N) per Method 49.
User input: bottom of conversation. Priority selector (default urgent). Sends to current room.
#Replaces
- tmux session bridge (xterm.js terminal emulation)
- Per-agent terminal windows
- Manual mailbox checking by agents
#Dependencies
- Agent runner:
scripts/agent_runner.py(Architecture 35) - Context distillation:
patterns/methods/49-return-budget.md(Veda) - Agent roster:
data/agent-roster.jsonl
Architecture 36 — Agent Router and Rooms — 2026 — Zachary F. Mainen / HAAK