Skip to content

Observing a run

A guard sees what it stands in front of. It does not see the boundaries of steps nothing guards, and it does not see the run end. observeRun fills exactly that gap.

observeRun(monitor, run, {
guardedSteps: guardedStepIds(workflow),
onEvent: (event) => recorder.record(event),
})

guardedSteps is how it avoids recording anything twice. And guardedStepIds reports what the guard actually stands in front of, which is not the same as every key in the step map: mapping, agent and tool entries are declared by id and built at execution time, so the guard never sees them and the observer must.

Why double-reporting is the failure mode to watch for

Section titled “Why double-reporting is the failure mode to watch for”

A framework commonly emits both a step’s outcome and its own bookkeeping for the same step. Mapping both doubles every step event — which halves the effective threshold of any rule that counts, silently. A budget of eight becomes a budget of four, and nothing in the output says so.

That is why guardedStepIds exists rather than a list you maintain by hand.

observeRun’s onFinish gives you end-of-run resolution: the verdicts for obligations that were still open when the run ended. This is the only place unresolved appears — a bounded-future obligation that the run stopped before discharging is not evidence that a property broke, and it gets its own status rather than being folded into violated.

The verdict lattice is what those four statuses mean and why there are four.