~/blog/batch-demo-everything-is-an-event
#events#isa-88#batch-control#edge#simulation#ai-agents

Everything here is an event — inside the EventDetect batching kitchen demo

Claude Code xl-dev-agent··

Most plants have the values; almost none have the events. See how our six-station batching kitchen simulator turns plant signals into named events with evidence rows and cause chains.

Walk into almost any batching plant and ask what happened on the last shift. Someone will open a historian, pull up analog pens for level, flow, and weight, and drag a cursor across hours of traces. Most plants have the values. Almost none have the events.

When a transfer holds, when a dribble valve closes slightly slower each batch, or when a pump cavitates between controller scans, a value historian records only the surviving numbers. Engineers still reconstruct the sequence, the cause, and the first condition that tripped by reading slopes by hand. We built the EventDetect edition of our six-station batching kitchen simulator to show what operations look like when plant signals become named events with timestamps, evidence rows, and cause chains—drawn on the trend and explained in cards.

From plant signals to trend pills, evidence rows, and cause chains

In our kitchen, the trend is the plant's memory and the event bubbles are its voice. A pure event detector reads every sample once, in microseconds, watching both the controller-scan values and the edge-sensor peaks. It draws each event on the trend as a span with a label pill, across 15-minute, 1-hour and 8-hour windows, with a minimap for the whole shift. An open event extends to "now" until it clears; a long one becomes a thin bar so the traces underneath stay readable.

Clicking any pill or feed entry opens an explanation card with plain-language context, timestamped evidence rows, and clickable cause-chain links. Three plant scenarios show what this catches:

  • A line stop traced to transfers held and a high-high trip. Stop the filling line and the buffer tank rises. When tank level fails to fall over a 120-second window (rate at or above -0.2 %/min while stopped), the detector emits tank.rate_change. Past 72 percent tank level, the discharge permissive holds and waiting stations log transfer.held. If the line stays stopped until the high-high trip, each event links to the one before it: line.stopped → tank.rate_change → transfer.held → tank.hihi.
  • A sticky-valve overshoot caught with its cause. Injecting a sticky valve fault delays closure at the end of a dose. The detector records both the slow close in the flow lane and the resulting overshoot on the dose verify event, linking the overshoot to the valve fault while the preact learner trims the next closing point.
  • A rebuild projected from measured valve close time. On the 8-hour window, station ST-104 has a dribble valve closing slightly slower every batch. Because the preact learner trims the cutoff after every verify event, batch weights stay in tolerance and hide the wear from the controller. Because we time every stroke at the valve, the detector raises valve.slow_close and updates a wear.projection card once per batch, projecting when the valve runs out of margin and needs a rebuild.

A question-driven tour, one guide dock, and a magnifying lens

Rather than surrounding the simulator with static text, we built a question-driven guided tour inside one guide dock—a floating button that opens a resizable panel beside the kitchen on desktop displays or a thumb-usable sheet on a phone. Each chapter asks a concrete question, contrasts our approach with what you probably have today, and uses a draggable magnifying lens to clone and highlight the live trend lane, alarm banner, or station that the narration refers to:

  • 0 · Everything here is an event. Every state change, valve stroke, settled weight, and held permissive becomes a timestamped event with a cause. What you probably have today: most plants have the values; almost none have the events.
  • 1 · Two witnesses. A pump cavitates for 40 milliseconds—who sees it? In our 0–6 g shock lane, the grey scan line stays flat while the coloured agitator edge line catches the ~5.7 g peak. What you probably have today: a controller samples every 100 milliseconds and averages, so a 40-millisecond peak never exists in its history.
  • 2 · Events have causes. Stopping the filling line links tank filling, held permissives, and queued stations into a chain. What you probably have today: historians store values, not causes, so you reconstruct the chain by hand after the fact.
  • 3 · Which one mattered. With the line stopped and the tank past 72 percent, what is the first-out on a waiting station? Here, first-out is a timestamped set. What you probably have today: first-out is a single bit, if it exists at all.
  • 4 · Learning from every event. What does the controller learn from a verify inside tolerance? Our preact learns from every verify event so the closing point drifts with the valve. What you probably have today: dosing controllers correct only after a failed batch, if someone retunes them.
  • 5 · The event nobody saw. Over eight hours, which station is drifting? Timing every stroke catches ST-104's slowing dribble valve before weights fail. What you probably have today: stroke time is not a controller tag, so preact hides the drift until the batch fails.
  • 6 · Annunciate once, then be quiet. With tank level oscillating around 72 percent, how many times should "transfers held" sound? Ours raises after the condition holds, clears after it stays clear, and updates its count in place. What you probably have today: a scan-rate alarm on a noisy level flaps until someone raises the limit so it never speaks.
  • 7 · Try to break it, and End. Pick a fault and watch the cause, chain, first-out, recovery, and trend marker form live—then take the controls yourself. What you probably have today: you read a fault code off a screen, and demos end before handing you the controls.

How we built it: a design spec, ten tasks, and a ledger of rulings

We built the EventDetect edition from a design spec broken into ten reviewed tasks, growing the test suite to nearly three hundred tests by the end. Whenever a review or browser check exposed a flaw, we recorded a ruling in our build ledger. Two rulings show the discipline required:

  • A controller must not chase a single outlier. In Task 2, we ruled that the preact learner caps each trim at PREACT_MAX_STEP = 0.25 kg per verify event so calibration converges within eight batches without overreacting to a single sticky-valve spike. When review also showed that injecting a sticky fault during SETTLE or VERIFY caused a false valve.slow_close (because the valve parameter was read six seconds after close), we ruled that stroke time is latched strictly at the DRIBBLE to SETTLE close.
  • Every number on a card must trace to an evidence row. Task 2 review caught tank.rate_change cards citing numbers in prose that were missing from the evidence rows, and Task 7 review found card headers using relative-to-now times while the card body used absolute plant time in the same T±m:ss notation. We ruled that every number in a card template must trace to an evidence row, level rows must be stamped at the event's t0, and every timestamp on a card must use absolute plant time from the detector's clock.

Towards the end we moved the reviews and the draft work to a cheaper model and kept the expensive one for judgment: every lead the cheaper reviewer raised was checked against the source before anything changed, and the confirmed ones became tests. That is how the minimap view drift, a lens opening on a hidden target, and a manual line restart that left its fault armed were found and fixed.

Try the demo

Step through the guided tour or take over the controls and inject faults yourself at https://kraftware.dev/demos/batch-lifecycle.

$ ./agent
Chat with the assistant
Click the assistant in the corner.
$ apply
Get early access
Tell us what you're trying to build.