← Work

A coaching agent safe enough to stand next to someone discharging a capacitor

ProjectToglo, FieldMate
RoleCo-founder and founding engineer
DatesApril to June 2026
Statusprivate (Toglo IP; design described, no code)
StackLiveKit Agents, Python, Claude Sonnet, Claude Haiku, Deepgram, ElevenLabs, Next.js

FieldMate is a voice and camera agent that talks an HVAC technician through a physical task while their hands are busy. Capacitor replacement, a heat-pump install, a maintenance checklist. The technician points a phone at the equipment, the agent sees what they see and speaks the next step. The interesting problem was never the LLM call. It was making a language model trustworthy enough to stand next to someone doing something that can hurt them.

The constraint

Some steps are irreversible or dangerous. Discharge the capacitor. Pull the disconnect. The workflow must not advance past those steps until the technician confirms they did them, and it must not be trickable into advancing. A model that is a little too agreeable will "helpfully" complete a step nobody finished. That is the failure mode the whole design is built against.

The first version made this impossible to solve. V1 was browser-only: the phone talked directly to a multimodal model over a WebSocket, no backend at all. It worked for espresso pulls and IKEA shelves. It had no server-side control point, so there was nowhere to enforce a gate and nowhere to observe what the model was doing. I rebuilt it with the brain on a server, which is what made a deterministic safety layer possible.

What I built

Four layers, ordered cheapest and safest first. The model is reached only for what needs judgment.

  1. A deterministic state machine owns the workflow position and every hard gate. No LLM involved in advancing a step in the normal case.
  2. A keyword fast path. "Next step" and "check my work" short-circuit before any API call. Zero cost on the most common turns.
  3. A small intent classifier (Haiku) decides advance, check work, or none from the transcript. Confidence threshold 0.8. On any error it returns none. It never raises.
  4. The coaching brain (Sonnet) runs a capped tool loop with tools like advance step, acknowledge hard gate, escalate to human.

The invariant that ties it together: the brain can never silently advance the workflow. Any advance the model proposes is downgraded to stay unless a human explicitly asked to move forward, by voice or button. The project's own engineering rules mark this as load-bearing and never to be removed.

The decision I would defend

The hard gate is an AND, not an OR, and it is monotonic toward closed.

The floor is pure code: a whole-token keyword match ("discharged") plus a whole-utterance negation scan. Provably correct, frozen by policy. But the floor alone cannot tell "discharged?" (a question) or "almost discharged" (a hedge) from a confirmation, because both contain the keyword and no negation token. So a second, narrow Haiku call answers exactly one question: did they confidently say they already did it? Threshold 0.85.

Both must agree to open the gate. Every error path fails closed. A degrading judge can only over-close, which is annoying, never under-close, which is dangerous. That asymmetry is why I could ship without a nightly recall harness for the judge and say so in writing.

Every hardening pass came from an adversarial review that found a real bypass. There were five. Each became a phrase in a 34-phrase bilingual adversarial corpus with monotonicity tests in CI, so a wrong judge can never open a gate the floor denied.

What the numbers say

MeasureBeforeAfter
Intent benchmark, 45 labelled cases35 / 4544 / 45
Implicit-intent subset, 20 cases12 / 2020 / 20
Brain p95 latency, measured baseline6.77 s
Test functions in the repo966
Adversarial gate phrases, EN + RU34

The Russian voice pack is the proof the design generalizes. The negation floor takes a pluggable tokenizer and negation set, so a Russian denial ("I haven't discharged it") is caught by the same whole-utterance scan with the Russian particle. Same mechanism, second language.

The debugging story

A production race: the agent sometimes spoke a preemptively generated reply that no longer matched the workflow state. I read the transport library's own source instead of guessing, matched it against Langfuse traces, and fixed the ordering. The monitoring that caught it wraps each health check in its own try/catch, so an outage in the monitoring vendor fires an alert instead of silently skipping. The monitor is not allowed to fail silently either.

The honest gaps

  • Golden evals are all-must-pass with no percentage thresholds. A nightly recall harness for the judge was designed and deliberately deferred, for the fail-closed reason above.
  • Vision test fixtures were tiny placeholder images. Pipeline plumbing was tested; real visual recognition was not.
  • Token and cache spans were computed but not emitted for a while. A cache regression would have been invisible until I wired them.
  • Camera frames had no timestamp for a while, so the brain could reason about a 30-second-old frame without knowing it was stale.

Why this matters beyond HVAC

New workflows are authored as YAML by sitting with a senior technician, not written as Python. Validate, regenerate the catalog, run the authoring harness. A PDF-or-video-to-workflow extractor drafts the first version with multi-pass consensus, and a person signs off on every one before it ships. Automation drafts. A person approves. That is the shape of every system I have built since.

Figures

1Deterministic state machineowns workflow position and every hard gate · no LLM0 ms · $0
2Keyword fast path“next step”, “check my work” · before any API call0 ms · $0
3Intent classifier (Haiku)advance / check / none · threshold 0.8 · fails to none~150 ms
4Coaching brain (Sonnet)capped tool loop · advance downgraded to stay unless the user asksreasoning
Cheap and deterministic first, the model only for judgment. Each layer is reached only if the one above it defers, and the brain can never silently advance the workflow.
input · a spoken phrase, e.g. “almost discharged”
Floor · code, frozen
whole-token keyword match
AND whole-utterance negation scan
provably correct · byte-for-byte unchanged
Judge · Haiku, narrow
did they confidently say
they ALREADY did it?
threshold 0.85
both must agree ( AND )
gate opens →
any error → gate stays closed
hardened by 5 real bypasses · 34-phrase EN + RU corpus · monotonicity tests in CI
The hard gate is an AND of a frozen deterministic floor and a narrow LLM judge. Every error path fails closed, and the composition is monotonic toward safe: a degrading judge can only over-close, never under-close.