Decision — executed commands are published
Status: accepted 2026-10-11. Amends egui applications (AGENTS.md §4.1).
The decision
Amended 2026-10-20 by commands take parameters and return results. The body is v2; v1 events are no longer emitted.
-
Every application publishes each command it runs.
- The topic is
<app>.command.executed, where<app>is the application's Bus service name. - An event means the command registry accepted the invocation and the command's handler returned.
- A command that is unknown or disabled is refused, and nothing is published.
- The event does not report the outcome of work the handler queued, such as a save that later fails. Outcomes travel by the application's own verbs and topics.
- The topic is
-
The body:
{ v: 2, run: "5f0c…", -- this process: a UUID drawn once at start seq: 812, -- per run, from 1, assigned in execution order at_ms: 1760160000123, -- integer ms since the Unix epoch, at handler return id: "file.open", -- the command's stable id origin: "menu", -- menu | shortcut | palette | ui | bus | macro params: null, -- accepted body, null for a nullary run or note() result: null, -- returned value; note() records null dropped: 0 -- events this run lost since the last one published } -
seqanddropped:- Numbering.
seqis assigned when the event is created, before it is queued, so the published stream may have gaps. - Loss. When the queue is full the oldest queued event is dropped and
a loss counter grows. The next event that is actually published carries
the counter in
dropped, and the counter resets. A gap inseqalways equals the sum of thedroppedvalues that explain it. - Ordering.
seq, notat_ms, orders a run's events: the wall clock can move. - A new run. A different
runmeans the application restarted, andseqstarts again at 1. Consumers key a sequence by the publishing service (and node) together withrun;runis opaque. - Reconnects. Events that could not be published while the Bus was away count as dropped.
- Unknown outcomes. A publish whose outcome is unknown (the
connection failed after the frame went out, or no answer came) may have
reached subscribers, so it is assumed delivered and not counted. If it
was in fact lost, a subscriber sees a gap that
droppeddoes not explain, the same as its own missed deliveries. Exact accounting there would need a broker receipt per event. - Subscriber loss. A subscriber that misses deliveries detects it from
seqgaps thatdroppeddoes not explain.
- Numbering.
-
originis the mechanism that reached the registry, not an earlier cause:menu: a menu row, including one chosen through synthetic input;shortcut: a key binding, including a synthetic key press;palette: the command palette or Help search;ui: any other control in the window, such as a toolbar button;bus:<app>.execute(or another verb whose documentation says it runs a registry command);macro: the shared macro runner. This value is reserved until applications have a Macros menu.
-
Parameters and result: the event carries the accepted params and the returned result, or a digest above a bound. Each field is measured using compact JSON (
serde_json::to_vec); a value above 4096 bytes becomes{ bytes, sha256 }, with a lowercase SHA-256 digest of those bytes. The accepted params are preserved verbatim; the registry cannot reserialise defaults applied inside a handler. Nullary params and bothnote()fields are null. -
Publishing never blocks the application.
- Events go through a bounded queue on the application's existing effect path.
- The topic is not retained.
- Events still queued when the application exits are lost, and nothing counts them: the next run starts afresh.
-
It will be implemented once. The UI-free command registry (
libs/command) will take the origin at dispatch and record each run. The shared Bus connection (thecitizenlibrary, or an application's own connection until it moves tocitizen) will publish the records. An application will not write its own publisher.
Why
Every action is already a command with a stable id, and agents already reach
every command over the Bus. Until now the commands a person ran by hand
stayed inside the process. Nothing could watch them, record them or learn
from them. Publishing each run closes that gap with one hook, and it is the
first half of how rec writes a person's work
as a script.
Consequences
- AGENTS.md §4.1 gains the rule.
Registry::executegains an origin, and the existing call sites state theirs.- Each application gets a test proving that a menu command and a Bus command
are both published with the right origin and consecutive
seq, and that a refused command publishes nothing.