RimZ
ReferenceCLI`rimz events`

`rimz events`

rimz events reads and writes the workspace's durable event log. follow prints each agent lifecycle transition and each signal as one JSON object per line, so a script…

rimz events reads and writes the workspace's durable event log. follow prints each agent lifecycle transition and each signal as one JSON object per line, so a script can react to agent state without reading panes. emit appends a named signal of your own and fires the loop tasks subscribed to it.

CommandWhat it does
rimz events followStream lifecycle and signal lines as JSON Lines.
rimz events listList recent signals from the active log.
rimz events emit <NAME>Append one signal and fire its subscribers.

All take the global flags; --root picks the workspace whose log they use.

All require an existing room. Outside a room they exit nonzero with no room at <root>: run \rimz start` there, or pass --root <room>; follow` refuses before polling.

List recent signals

rimz events list
rimz events list --signal 'ci.*' --since 2h -n 10
rimz events list --signal deploy.finished --json

list prints signals newest first, with columns WHEN, SIGNAL, SOURCE, and SUBJECT. The subject is the same text that follows the signal name in a delivered notification, or - when empty. Lifecycle records are not listed.

FlagEffect
--signal <NAME|FAMILY.*>Keep an exact name or every name in one family, using the subscription selector grammar.
--since <DUR>Keep records within this duration of now; integer s, m, h, or d.
-n, --limit <N>Maximum records after filtering; default 20.
--jsonOne signal line per record, the same schema as follow, newest first.

Only the active event-log generation is searched, never archives, even when --since reaches farther back. When no signal matches, text output names the age of the active log's first record (or says the active log is empty). JSON output is empty.

Follow the event stream

rimz events follow
rimz events follow --replay | jq 'select(.event == "signal")'

follow starts at the live edge and prints only what is appended after it starts. --replay first prints the current log generation from its beginning, then keeps following. The log rotates into an archive once it reaches 64 MiB, or when you run rimz workspace rotate-events, and archived generations are never replayed. A follower that is already running when the log rotates reads the rest of the old generation before it moves to the new one, so it misses nothing.

FlagEffect
--replayPrint the current log generation from its first record before following.
--signal <NAME|FAMILY.*>Print only matching signal lines, with no lifecycle lines.
--jsonAccepted and ignored: JSON Lines is the only output.

The stream follows these rules:

  • stdout carries one JSON object per line, flushed as each line is written.
  • stderr carries warnings, prefixed rimz: warning:, when an archived generation is missing or disappears before it is read, or the log generation moves backward. The stream continues after each warning.
  • Ctrl-C, SIGTERM, or a reader closing the pipe ends the command with exit code 0. A log that cannot be read ends it with exit code 1.
  • The follower checks the log every 250 ms. Set RIMZ_EVENTS_POLL_MS to a positive integer to change the interval; any other value is ignored and the default applies.

Each line's event field says which shape it has: lifecycle or signal. The log's other records (messages, launches, attaches) are not printed. How the follower folds the log into lines is in publishing transitions.

Lifecycle lines

A lifecycle line is one transition of one agent or provider-native subagent:

{"event":"lifecycle","v":1,"event_id":"evt_…","at":"2026-06-01T12:00:00Z","workspace_id":"ws_…","kind":"claude","agent_id":"session-1","agent_name":"coder","signal":{"signal":"turn_ended","errored":false,"parked_on_background":false},"prior_status":"running","status":"success","phase":"idle","transition":{"kind":"normal"},"compaction_closed":false,"waiting_cleared":false}
FieldMeaning
eventlifecycle.
vSchema version of the lifecycle line, currently 1. A breaking change to the line increments it.
event_idIdentity of the durable log record.
atRecord timestamp, RFC 3339.
workspace_idWorkspace whose log holds the record.
kindAgent kind, such as claude or codex.
agent_idProvider session id of the agent or subagent.
agent_nameThe agent's handle without @, when the record carries one; omitted otherwise.
parent_agent_idSession id of the root agent, on a subagent's line; omitted for a root agent.
signalThe lifecycle signal the agent reported, tagged by its signal field, with that signal's own fields. The signals are listed in the agent model.
prior_statusStatus before this transition; omitted when the follower has no earlier state for the agent, including when the agent has fallen outside the 256 most recently ended agents. At the live edge that state comes from the workspace's current rollup; under --replay it builds up from the replayed records, so an agent's first replayed line has none.
statusStatus after the transition, as the lifecycle records it (for example running, waiting, success).
phaseTurn phase after the transition: idle, reasoning, acting, or parked.
transitionHow the state machine classified the signal: {"kind":"normal"}, {"kind":"reconciled","from":"…","reason":"…"}, or {"kind":"ignored","reason":"…"}.
compaction_closedtrue when this signal closed an open context compaction.
waiting_clearedtrue when this signal moved the agent off waiting.

A lifecycle record with no agent session id prints no line.

Signal lines

A signal line is one signal appended to the log, by emit or by RimZ itself:

$ rimz events follow --replay
{"event":"signal","v":1,"event_id":"evt_01a06d7d112171d0bdaceff9e4a3c6aa","at":"2026-09-04T17:35:08.065761436Z","workspace_id":"ws_f89e49906df0621ad2765112","name":"deploy.finished","payload":{"env":"prod","version":"1.4.2"},"source":"cli"}
FieldMeaning
eventsignal.
vSchema version of the signal line, currently 1.
event_idIdentity of the durable log record; emit prints the same id.
atRecord timestamp, RFC 3339.
workspace_idWorkspace whose log holds the record.
nameSignal name, such as deploy.finished or ci.failed.
payloadThe signal's top-level JSON object; {} when it has none.
sourceWho appended it; see the next table.
sourceAppended byNames
clirimz events emitAny valid name outside the reserved families.
forgeThe room's sidebar, when a worktree branch's CI or pull request changes stateci.passed, ci.failed, pr.opened, pr.merged, pr.closed, pr.behind, pr.conflicted, pr.queued, pr.dequeued
gitThe room's sidebar, when the project trunk ref movestrunk.moved
worktreeSuccessful managed worktree creation or removalworktree.created, worktree.removed
lifecycleThe agent lifecycle hook, when a team member's transition changes its cohortteam.idle, team.waiting, team.failed, team.ended
teamA team stage flip, and the re-wake of the stage owner when it is resumedteam.stage
watchThe watcher of a rimz wait command, check, PID, or file watch, at a check-in and its final outcomewait.<task-name>

Two kinds of signal fire subscribers but print no signal line. An agent.* signal is derived from a lifecycle record, and that record's lifecycle line is its durable trace. A wait.<task-name> delivery that RimZ sends because the watcher itself died is not written to the log. A watched command's exit code travels only in the delivered message, never in the signal line's payload.

Emit a signal

rimz events emit deploy.finished
rimz events emit deploy.finished --json '{"env":"prod","version":"1.4.2"}'

emit appends one signal record with source cli, then fires every loop task subscribed to it in this workspace, and prints the record id, the count of tasks it fired, and one indented line per task:

$ rimz events emit deploy.finished --json '{"env":"prod","version":"1.4.2"}'
emitted deploy.finished (evt_01a06d7d112171d0bdaceff9e4a3c6aa) · fired 1 task
  wait-noble-lane

With no matching subscriber the line reads no task is subscribed to it and nothing follows. A subscription to a sibling name, such as deploy.failed, does not count. With matching subscribers but no started task it reads fired no task. Each matching task that did not start gets an indented <task>: did not start line after any fired task names. Multiple started tasks read fired N tasks.

ArgumentRule
<NAME>Dot-separated segments, at most 64 bytes in total. Each segment starts with a lowercase letter or digit and contains only lowercase letters, digits, -, and _. The first segment is the signal's family.
--json <OBJECT>One top-level JSON object, at most 64 KiB as typed (whitespace counts). Omitted, the payload is {}.

Firing happens inside the emit process, with no daemon and no queue. It needs an existing room but no running agent. It spawns one detached run for each enabled subscription that exists in this workspace at that moment; a task added afterwards never sees the signal, and nothing replays it later. A disabled or paused task, or a project task whose project is untrusted, does not fire. What a subscription matches, what it delivers, and when a delivery reaches its agent are in rimz loop signals.

emit refuses these inputs with exit code 1, before it writes anything:

InputError
A name that breaks the ruleserror: invalid signal name `<name>`; use lowercase dot-separated words, at most 64 bytes
A name in a reserved familyerror: signal name `<name>` is reserved for RimZ
A payload over 64 KiBerror: --json payload exceeds 64 KiB
A payload that is not valid JSONerror: parsing --json payload, followed by the parser's message
A payload that is JSON but not an objecterror: --json payload must be a JSON object

If firing fails after the append, emit exits 1 with firing signal tasks, and the signal record stays in the log.

Reserved families

Seven families belong to RimZ, and emit refuses every name in them, including names RimZ never produces (ci.custom):

$ rimz events emit ci.passed
error: signal name `ci.passed` is reserved for RimZ
FamilyNames RimZ producesSource
agentagent.started, agent.idle, agent.waiting, agent.failed, agent.endedDerived from lifecycle records; no signal line.
teamteam.idle, team.waiting, team.failed, team.endedlifecycle
teamteam.stage (payload)team
cici.passed, ci.failedforge
prpr.opened, pr.merged, pr.closed, pr.behind, pr.conflicted, pr.queued, pr.dequeuedforge
trunktrunk.movedgit
worktreeworktree.created, worktree.removedworktree
waitwait.<task-name>watch

The sidebar appends forge signals by running emit with a hidden --source forge flag, which accepts only the nine ci and pr names above. The hidden --source git accepts only trunk.moved; there is no CLI worktree source. These flags perform no caller check, so reserved families prevent name collisions, not authenticated provenance. What each built-in signal's payload carries is in the loops guide and the signal vocabulary.

pr.opened reports a newly observed open PR after a successful prior probe, not a PR already present when monitoring starts. trunk.moved reports a changed commit on the same trunk ref. Worktree signals follow successful managed operations, with creation after its hook succeeds; reuse and failed-creation rollback are silent. See trunk and worktree signals for the producer contract.

pr.behind and pr.conflicted are GitHub-only signals for open PRs. They fire on entry (first sight included), or when the PR head changes while still behind or confirmed conflicting; repeated readings of the same head do not repeat the signal. Conflict detection waits for GitHub's settled mergeability reading. Both carry the pr.merged payload with state: "open", plus pr_head (the forge head, or the head where conflicts were confirmed), base when known, and behind_by for pr.behind only. head remains the local worktree HEAD. Match them by branch or path just like pr.merged.

pr.queued and pr.dequeued are GitHub-only signals for an open PR and the merge queue. pr.queued fires when the PR enters the queue, and again only for a later entry; pr.dequeued fires when it leaves the queue while still open, and again only for a later removal. Both also fire the first time a poll reads the state on a checkout the room was already watching. A room that opens on a PR already queued or dequeued hears nothing: its first poll only sets the baseline, so read the standing state with --when pr.queue=dequeued instead. A PR that merges out of the queue emits pr.merged and no pr.dequeued. Both carry the pr.merged payload with state: "open", plus pr_head (the PR's head commit) and base when known. pr.queued adds queued_at; pr.dequeued adds dequeued_at, reason when supplied (GitHub's own string, such as failed_checks or manual), and queue_checks_url, the checks page of the queue's own commit, when GitHub names that commit. The timestamps are GitHub's.

On this page