Skip to content

[feature] v11 migration — room protocol substrate (rooms/messages/decisions + sync_rooms() + /rooms dashboard) #65

Description

@JayantDevkar

Context

Extending karma to serve as the durable substrate for agent-coord, a new multi-agent coordination product. Instead of standing up a standalone decision store, we reuse karma's single-DB + existing indexer + existing dashboard primitives. Integration contract was answered in docs/features/2026-04-24-karma-integration-answers.md (this repo).

Updated 2026-04-24 after a senior-designer review pass on the agent-coord design. The body below is authoritative; earlier comments on this issue contain the deltas that led to these updates. Key post-review deltas baked in: decision mirror columns renamed for external-system pluggability, agent_presence has an explicit @human row via is_human column, agent-coord layer references renumbered to the 5-layer model.

What I'm asking for — three independently mergeable pieces

1. v10 → v11 schema migration

Five new tables (no collisions with existing schema; none of these concepts exist in karma today per the integration answers §Q2, §Q10):

  • room — PK id (external ticket id, e.g. Linear LIN-4821 or GitHub owner/repo#N), title, status, created_at, closed_at?
  • agent_presence — FK room.id, agent_id ("{repo}:{branch}" for Claude sessions; literal "human" for the director — stable across commits AND across session restarts; not keyed on session_uuid), repo (NULL for human), branch (NULL for human), session_uuid (FK sessions.uuid; NULL for human), is_human BOOLEAN NOT NULL DEFAULT false, joined_at, joined_at_commit (NULL for human), last_seen_at_commit (NULL for human), left_at?
  • message — PK id UUID v7, FK room.id, thread_id?, in_reply_to?, from_agent_id, to_agents JSON, mentions_attempted JSON, type, body (TEXT or JSON for type=decision), confidence, schema_version, created_at
  • citation — PK, FK message.id, urn, node_kind, resolved_at_commit, retrieved_via; UNIQUE(message_id, urn)
  • decision — PK id UUID v7, FK room.id, FK message.id question_id, FK message.id answer_id, body, made_by, confidence, valid_from, valid_until?, FK decision.id superseded_by?, status, mirror_state (pending / synced / failed), mirror_attempts, mirror_last_error?, external_system TEXT NOT NULL DEFAULT 'linear' (enum-ish: linear / github / jira / …), external_ref_id? (comment/issue id in external system; NULL until synced), external_ref_url? (cached URL for dashboard rendering)

Written as an idiomatic idempotent migration in api/db/schema.py (append v11 block, bump SCHEMA_VERSION). All IDs are UUID v7.

On INSERT INTO room, also insert the synthetic @human presence row:

INSERT OR IGNORE INTO agent_presence (
  agent_id, room_id, repo, branch, session_uuid,
  is_human, joined_at
) VALUES (
  'human', ?room_id, NULL, NULL, NULL,
  true, CURRENT_TIMESTAMP
);

This lets every consumer (dashboard, escalation rules, reconciler) query SELECT * FROM agent_presence WHERE room_id = ? and get a complete roster without special-casing the human. Without this row, every consumer has to UNION ALL SELECT 'human' which is a papercut that compounds.

2. sync_rooms() indexer module

New module under api/db/ parallel to the existing session indexer. Reads append-only JSONL files at ~/.claude/rooms/<ticket>/messages.<agent_id>.jsonl (one file per agent per room — matches karma's one-writer-per-JSONL idiom).

Invocation: primarily hook-triggered via UserPromptSubmit (called from the agent's Claude Code hook); secondarily via the 300s polling timer in api/main.py as a safety net.

Ingest semantics:

  • Parse JSONL lines, validate schema_version, merge across per-agent files sorted by (created_at, id).
  • Per message: INSERT OR IGNORE INTO message (id, ...) keyed on UUID v7 — idempotent replay.
  • For type=decision messages: wrap message INSERT + decision INSERT in a single transaction.
  • Track last_indexed_message_id per room JSONL file for O(tail) resume on restart.
  • Enforce citation requirement at ingest (not speak-time): reject type=answer messages lacking ≥1 stable-URN citation; write a type=status response back into the room.
  • Evaluate escalation rules in the same ingest pass (three if-statements: explicit @human, implicit all-unsure, time-based NOW() - MAX(created_at) > N). No separate worker.

3. /rooms dashboard page in SvelteKit frontend

Per integration answers §Q6: ~700 LOC, ~1–2 days leveraging existing component library (PageHeader, StatsGrid, FilterControls, etc.). Three files:

  • routes/rooms/+page.svelte — list view, filtering, search
  • routes/rooms/+page.server.ts — data loader
  • Nav link added to lib/components/Header.svelte

Also: routes/rooms/[id]/+page.svelte for a single-room timeline view (can ship as follow-up if the estimate expands).

External-mirror reconciler (out of scope for this PR; flagged for awareness)

Once decision rows exist, a reconciler (separate module, can ship in a follow-up PR) reads WHERE mirror_state = 'pending', dispatches on external_system, and POSTs a ticket/issue comment with a hidden HTML marker <!-- decision:$id --> for idempotency. MVP backend is linear; github would use the same marker pattern, just a different API endpoint. No schema migration to add new backends.

Why this matters

Multi-repo tickets currently force users to relay prose manually between Claude sessions (15–30 copy-pastes per Airflow-DAG-shaped ticket). agent-coord eliminates the relay via structured, grounded messages. karma is the durable substrate because:

  • Single global SQLite already exists — no new infra
  • Existing migration idiom accepts append-only additions cleanly
  • Existing dashboard gets free rendering for a new entity type
  • Session identity (sessions.uuid) is already stable and reusable

karma SQLite is the authoritative read surface. JSONL is the write-log / durability seam only — consumers (dashboard, reconciler, escalation rules) always read from karma.

Acceptance criteria

  • Schema v11 migration applies idempotently on existing databases
  • agent_presence has is_human column; synthetic @human row inserted on room creation
  • decision has external_system, external_ref_id, external_ref_url columns (no linear_comment_id)
  • sync_rooms() ingests a test JSONL file and produces expected rows, idempotent on replay
  • Hook-triggered invocation works from a minimal shell harness
  • Citation requirement enforced at ingest for type=answer
  • Escalation rules (explicit / all-unsure / time-based) evaluated in the same ingest pass
  • /rooms route renders a room's message timeline with in_reply_to + pins cross-links
  • Existing session indexer, dashboards, and tests remain unaffected

Parent tracker

JayantDevkar/git-radio#3

Reference (design, lives in git-radio)

docs/agent-coord/proposal.md:

  • §4 (architecture, per-agent JSONL transport, karma as authoritative read surface)
  • §5 L1 (participants & routing; @human row, addressing grammar)
  • §5 L2 (message schema with UUID v7)
  • §5 L4 (delivery, attention & escalation — fail-open hook contract + escalation rules)
  • §5 L5 (closure — atomic message+decision INSERT; external-system reconciler)
  • §6 (decision schema with external_* columns)
  • §7 (MVP boundary)
  • §8 (full message-by-message walkthrough with real SQL)

docs/agent-coord/system-design.md has Mermaid diagrams (component map, 5-layer stack, message lifecycle, state machines, end-to-end walkthrough).

Branch: claude/review-agent-coordination-docs-7IPKY.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions