How a card gets its state¶
Optional reading
You don't need any of this to use Romp. Describes the system as of 2026-07-20; the behaviour it documents moves, so treat anything here as a snapshot rather than a contract.
The state model, the "constitution": what moves a card, every chip it can wear, and the edge-case rules. Companions: judge-pipeline.md (the diagram map) and judges.md (who the judges are).
The five layers¶
- Raw logs (romp only reads): the Claude Code transcript per session, the states log (working/waiting/idle), the postal log.
- The event model (pure parse, recomputed, never stored): atoms become segments (one input + its work), segments become turns. Authors come from markers: human, romp injection, peer, system.
- The judges (small LLM calls) annotate: the index tier writes captions and archives (text only); the triage tier maintains the one piece of moving state, the goal store.
- The goal store: a tree of goal nodes per session. Each node's state
lives in its diary (
log), an append-only list of events{ev_t, src, kind, why, seg, at}. src is planner / closer / unblocker / courier / nudge / interrupt / romp / user / agent (the grouper never judges); kind is done / block / reopen / unblock / clear / settle / dismiss. Markers:msg(a user message rides this reopen),undo(an undo-clear restore),synth(reconstructed by migration, never witnessed).record_verdict()is the only door: it gates, appends, and materializes the node's cached flags in one call. Every derived key (nodeComplete,blocked,cleared,doneWhy,blockWhy,followupAt,followupPending,settledAt,settledDone,deltaSince) is a cache the fold rewrites. The write seam: every node is aGuardedNode, and assigning a diary-owned key outside the diary/cache layer raises at the write site. - The read side (kernel
build_feedto the browser): maps states to columns, adds live floors and chips, renders verbatim with short-lived optimistic predictions.
The fold: how a diary becomes a state¶
Sort events by evidence time (ev_t; arrival at breaks ties) and replay:
- A
donelands; ablocklands; areopen/unblockopens; aclearclears. - Snapshots: provisional flips restore what they displaced. A
clearsnapshots the state it covers, and an undo-reopen restores it, so a cleared completed card comes back completed, never "open". A msg-reopen (the optimistic flip when you reply to a card) snapshots too, and a plannerdismiss(the pivot verdict: that reply started its own thread) restores the original state and settle stamp. The pivot's new goal still stays with the card it replied to (the follow-up tie): it groups under the cited card's umbrella, or a fresh one wearing its title — the judge picks the form of follow-up work, never whether it stays together. - Settle is an event (the moment the card entered the Completed
column).
settledAt,settledDone, anddeltaSincederive from it: the newest un-reopened settle is the column-entry stamp, and the settle a reopen ended becomes the delta boundary for the re-distilled takeaway. - Held: an unanswered user reopen holds the node open; no bottom-up re-completion can overrule "I said this isn't done". If the reopen carried a message, the card also wears the "Followed up" chip. Any later judge/agent/romp event answers both.
- The user floor: a user event's time floors judge evidence. A judge
doneneedsev_t >= floor(equality lands: the resolving reply shares the stamp); a judgeblockneedsev_t > floor(equality voids: that block was computed from the very ask you answered). - Replay-safe by construction: a stale verdict arriving late sorts into its historical place and loses. Shuffling a diary never changes its fold (property-tested).
The tree, then the column¶
- A card is blocked if any open descendant is blocked. A parent is done if
ruled done or all children are done; a parent's done shadows still-open
sub-steps (display-resolved, reversibly:
rolledUp). - Agent override: an open item on the agent's own to-do list pins the card in Working over any judge done; releases the instant it's checked off.
- Column ladder: Cleared > Blocked > reply-in-flight (Working) > Completed-and-settled > Working. Settled means the ruled-done goal is no longer the session's focus (or the session closed). Sticky means once shown Completed, a mere re-touch cannot flap it back; only a real reopen does.
- Sorting inside columns is stamp-driven: newest at the bottom; a moved or reopened card lands at the bottom because its stamp is "now".
What moves a card¶
Judge rulings at segment/turn end · your reply (any column; reopens instantly, optimistically, moves the card to Working, and clears blocks across the card's whole subtree (you reply to the card, never to its blocked sub-goals) · Clear / Undo clear · Resolve · the agent checking off its to-dos · a peer completing delegated work (courier link-back) · a failed auto-nudge (records a block) · the settle moment · the live floors below.
Every chip, badge, and visual state¶
Column-forcing floors (live events, beat the stored state):
- picker / approval: the session is stopped right now on a question or permission prompt. Needs-you; a placeholder card is synthesized if no goal exists yet.
- API error: transient errors stay in Working with a Retry; a "prompt too long" error is on you, so Needs-you.
- Parked handoff: a message sent to a dead session becomes a Needs-you card (revive to deliver, or dismiss).
Chips on cards:
- interrupted (yellow): you stopped the session mid-turn and haven't spoken since; auto-nudge holds off until your next message. Yields to stalled.
- stalled: the one auto-nudge didn't resolve the goal; it is never
re-asked, and the failure records a block (src
nudge) so the card sits in Needs-you with this chip as the explanation. Yields once a real judge verdict takes over. Tooltip carries the nudge history. - re-judging (dotted): you answered a soft-blocked card with a targeted reply; it de-urgents into Working pending the re-judge.
- Re-judging swirl: a plain thread reply is in flight on a blocked card; returns on its own if the judge holds the block.
- Awaiting background work: dispatched or delegated work is outstanding. A Working flavor, never Needs-you, since only humans block. It reads as a box in the card body rather than a header chip; the header chip was removed in July 2026 for saying the same thing twice.
- Awaiting peer: an unanswered question is out to a live peer, named in its own color. When the wait is mutual, the same chip reads Deadlock peer instead.
- warning (yellow, clickable): a judge stamped an anomaly (e.g. a cite-miss); click for detail.
- working dot before a session name: that session is working right now.
- provisional ghost (dim, dashed, "Analyzing:"): a live prompt the planner hasn't classified yet; replaced by the real card.
- Followed up (modal tree, per sub-node): that sub was optimistically reopened by a per-sub follow-up.
- Group cards: sibling goals minted by one typed turn fold into one card (worst member's column); umbrellas nest related tops (grouper/consolidator; reorganization only, never status).
Optimistic predictions are client-side and kernel-reconciled: a follow-up/move flips the card instantly; if the kernel doesn't confirm within a beat it reverts with a toast, never silently.
Edge-case rules¶
- Authority: user > agent > judges; newer evidence wins within a rank.
The
>=/>floor asymmetry above is deliberate and tested. - Only a human blocks. Peers, CI, builds, and agents mean Working.
- Done resolves eagerly, blocking is conservative ("when in doubt, omit"); the error costs are asymmetric: a wrong done costs a glance, a wrong block costs an interrupt. Omit is not a dodge: a stalled omit escalates (nudge, forced resolution, block) in bounded steps.
- Cleared is sealed forever; a follow-up to a cleared card becomes a fresh goal.
- Every verdict goes through
record_verdict. A reopen carriesunblockevents for blocked descendants and ancestors; every unblock is an event. - Reopens un-resolve only auto-rolled (
rolledUp) children; genuinely-done leaves keep their state. Re-completions re-summarize only the new stretch (deltaSince). - Identity: identical prompts in different turns are different work
(twins); same-second identical bursts plan once; any seg-id-derivation
change ships a placements migration (
placementsV, currently v2);tests/test_placements_canary.pypins the derivation. - The auto-nudge fires once per genuine stall, never re-arms off romp's own turns, is suppressed while interrupted, and its failure becomes a block.
- Diaries are capped (
LOG_CAP64, oldest dropped,logTruncmarks it). Migrated (synth) events never fake timeline judging marks. - The migration window is closed:
migrate_all_storessweeps every live + archived store at kernel boot (idempotent). A node born in the diary era carries"log": []from birth; a verdict-flagged node with no diary is frozen, never derived-and-wiped, and surfaces loudly in judge-errors.jsonl. Retired flags (everDone,logBorn) are popped by the sweep. /clearis an episode boundary, not a deletion. A new null-rooted transcript head (vs the episodes log) settles the session's still-open tops — a romp-authored clear ("dropped when the conversation was cleared"), sealed like the mute path: no agent notify, no delegation cascade; completed cards stay. Placement fuzzy-matching is scoped to the current episode (a retyped prompt plans again; exact hits still dedup, so archived nodes never re-mint). The timeline draws a seam per boundary;plans/clear-episodes.mdhas the full design.