Skip to content

State and lifecycle

A channel’s entries say what happened. Some facts are about what is true now: a question is waiting on someone, an answer is pending, a file is leased, a ledger has been reconciled. A log alone can’t answer “what is still open” cheaply, and can’t enforce expiry or mutual exclusion. Those facts are state records.

A state record is keyed per channel and holds one current fact:

Field Meaning
key Unique within the channel, such as cursor-rule
kind Consumer-defined, such as sdd.needs-input, truss.pending-answer
status open or resolved
subject The participant it waits on or is held by
entryId The entry it is about, if any
value Any JSON
Terminal window
cynapse state set review-12 cursor-rule --kind demo.needs-input --status open --subject carol
cynapse state list --status open --subject carol # what is waiting on carol
cynapse state set review-12 cursor-rule --kind demo.needs-input --status resolved

Every transition is also written as an entry (cynapse.state.changed, with from and to), so the record says what is true now and the channel says how it got there. Open records appear in the channel’s briefing.

Nothing resolves a record automatically. A reply isn’t always an answer, so whoever answers or gives up resolves it.

A channel has one lifecycle state: active by default, and otherwise any string its consumer uses. The seed uses paused, closed, escalated and reconciled.

Terminal window
cynapse state lifecycle review-12 reconciled
cynapse channel list --state reconciled

A lifecycle change is written as a cynapse.state.changed entry. It is set with state lifecycle, not state set.

When a mission ends, its raw ledger has done its job, but the distilled record has to survive, and the distilled record is a view over the raw channel. So cleanup marks the channel reconciled and doesn’t delete it.

What the design adds on top, not built yet:

  • marking a channel reconciled makes its distilled view the default;
  • physically removing raw entries is an optional retention step. If it is ever taken, the channel records the seq ranges it removed, so a reader can tell “not received yet” from “removed on purpose”.

Leases are state records with a TTL, an exclusive flag, path patterns, a release time, and a way to repair orphaned leases, following mcp_agent_mail’s design. They aren’t built. Leases are for coordination, such as who holds a task or a file. They are not how a session’s liveness is tracked: a runtime asserts that itself (cynapse and the runtime, proposed).

A “happens at most once” write, such as a single ruling on a decision, needs a conditional append that the store doesn’t have yet (#19).