Agent Router and Rooms

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,…

#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):

  1. If urgent queue non-empty → take from urgent. Always.
  2. 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.
  3. 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)

FieldTypeDescription
idstringEvent identifier — uuid4() now; SHA256 of content in Filix (see Forward declarations below)
sigstringAgent signature — "" now; cryptographic in Filix (see Forward declarations below)
type"dialogue" \"tool" \"mailbox" \"pass" \"system"Event kind
roomstringRoom name
fromstringSource — agent name, "user", or "router" (for system events)
priority"urgent" \"normal" \"background"Message priority as string
tsstringISO 8601 timestamp
turnintTurn 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."
}
FieldTypePresentDescription
chunkstringDuring streamingText fragment for live display
doneboolOn completiontrue signals final event
contentstringOn completionFull 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
}
FieldTypeDescription
ixint§N index — monotonic per room, assigned to every non-dialogue event
tool_namestringTool invoked: Agent, Read, Write, Bash, etc.
argsobjectArguments passed to the tool call
resultstring \nullRaw tool output. Archived to sidecar as S{ix}. Set on completion.
labelstring2-4 word name for the expander header
stubstring \nullOne-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_msint \nullElapsed 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
}
FieldTypeDescription
ixintSame §N counter as tool events — shared sequence
subjectstringDispatch subject line — serves as both label and stub
bodystringFull message body. Archived to sidecar. GUI shows on expand.
mailbox_idintReference 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

  1. ix is per-room, monotonic, starts at 1 for each room's lifetime.
  2. Only tool and mailbox events get an ix. dialogue, pass, and system events do not.
  3. The router assigns ix, not the agent. Agents reference §N in their text; the router resolves which N maps to which event.
  4. On agent restart (new session in same room), ix continues 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 ! (Material priority_high icon)
  • Background-only rooms marked · (Material low_priority)
  • Normal/no pending: no indicator
  • Unread count shown per room

Conversation view:

  • dialogue events: plain text, inline
  • tool events: collapsible expander, neutral style. Collapsed = stub label. Expanded = full output.
  • mailbox events: 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