2026-10-15 · ai engineering · manufacturing

An agent that investigates a bad shift — and isn't allowed to do arithmetic

The corrugator shift investigator is a worked example of how I think an industrial AI agent should be built: the model decides where to look, the application decides what the numbers are.

Repo: justfetz/corrugator-shift-investigator. Python, SQL, synthetic XML data, an optional model adapter, and a workbench UI. Everything below is public and runnable offline.

Try it

This is the actual workbench from the repo, running on Cloud Run with the free offline planner — no model, no key, synthetic September 2026 data. Pick a day and shift, ask "Why was second shift slow? Chart downtime by reason.", then open How the agent got here to see every tool call, its SQL, the bound values, and the rows that produced each number. It scales to zero between visitors, so the first load can take a few seconds.

live · synthetic data · offline planner · nothing here is a real plantopen as its own page →

The question

A corrugator superintendent comes in and asks: why was third shift bad?

That question has a shape. "Bad" means some combination of speed below target for the grade mix, downtime above the 2.5% maintenance or operator thresholds, trim above 3.25% on a setup, dry-end waste above 1%. The answer is in the shift's run timeline, its downtime events, and its waste events — if someone pulls them, compares them against the right targets, and knows that a 500 ft/min double-wall run can be on target while a 725 ft/min single-wall run is behind.

That's a job a language model is good at directing and bad at doing. So the design splits it.

The split

The agent loop is deliberately narrow:

  1. The model receives the question and the list of tools it's permitted to call.
  2. It either requests one tool call or gives a final answer.
  3. Application code validates the call — tool name, argument types, ranges — before anything runs.
  4. The tool runs a parameterized query against a read-only dataset and returns structured evidence with references.
  5. Repeat until the model answers or a step limit is hit.

The tools are things like get_shift_kpis, get_run_timeline, compare_shifts, get_loss_breakdown, get_process_rule, and render_chart. Every number the model sees was computed by code I wrote and can test. The model cannot run SQL it composed, cannot execute Python, cannot touch the shell, and cannot emit HTML or JavaScript into the report. It can only pick from a menu and interpret what comes back.

Why this matters. A model that can do arithmetic will occasionally do it wrong, and a report that's 95% right is worse than useless on a plant floor — nobody can tell which 5% to distrust. Taking arithmetic away from the model doesn't make the agent dumber. It makes every figure in the output something a human can trace back to a tested function and a row in the data.

Evidence, not narrative

The output is a report, but the report is built around evidence links. Each finding points to the tool call and filters that produced it; the charts (bar, trend, timeline) carry the same references; an analysis-steps panel shows the path the model took. The pattern for every answer is: recorded finding → investigation question → human verification.

That last step is not a disclaimer. The README says it directly: recorded defects such as warp are observations, not proof of a mechanical root cause. The agent's job is to surface what the data shows and frame the next question for the person who can walk out to the machine. It is not to declare causes it can't verify.

The data is synthetic, on purpose

All production data is generated — grades, widths, speeds, downtime, waste — from a seeded generator, so runs are reproducible and no real plant's numbers are involved. The process rules (shift boundaries, knife assignments, what counts as a setup) came from an interview with someone who runs these lines, so the shape of the data is real even though the values aren't.

Synthetic data also makes evaluation honest. Each scenario has an answer key the agent never sees. You can ask "why was this shift bad," read the report, and check it against what the generator actually injected.

The input format is XML, which means XML is treated as hostile: external entities and DTDs are disabled and the parser validates before anything else reads the file. That's a small thing that gets skipped constantly.

What it doesn't do

I'd rather list this myself than have someone else find it:

Why build it this way

This is the pattern I'm carrying into Talk to My Machine: a deterministic optimization and analysis layer that owns the truth, and an agent layer that owns the conversation. The shift investigator is the public, runnable version of that idea at small scale. If the pattern holds here — bounded tools, application-owned numbers, evidence over narrative, limits stated up front — it holds when the data is real and the stakes are a shift's worth of board.