The Lab · 01 · MIT licensed

loop.js

The rules I argue for in design reviews, written as functions so they can be tested instead of debated. Four decisions that sit between a model and a person: which verb a score becomes, whether its confidence has earned belief, when to say nothing at all, and what to show while it thinks.

What this is not. It is not a UI kit and not a model. There is no network call, no DOM, no state, no framework — four pure functions and their tests. I own the decision logic and the interface; whether the model is right is the ML team's result to defend. The visual layer is deliberately left to you, because the verb matters more than the chip it ships in.
The principle this artifact proves

A design opinion becomes falsifiable the day it compiles.

Use it

No install step, because there is nothing to install

One file, no dependencies, standard ES module. Copy it into a project or import it from here.

// A score never reaches a screen naked — it becomes a verb first.
import { decide, calibrate, abstain, disclose } from './loop.js';

const d = decide(0.92, {
  reversible: true,
  signals: [{ name: 'margin', value: '12%' }, { name: 'stale data' }],
});

d.verb              // → 'act'
d.requires.override // → true   (an act you cannot undo is not an act)
d.explain           // → 'Confidence 0.92 at or above 0.85: safe to act, with an override.'

The four decisions

Every control below runs the real module

Nothing here is mocked. The page imports loop.js and calls it as you move the inputs, so what you read is what the function returns.

decide(score, options)

Pattern · Act / Review / Ignore + Reversibility

A naked number ends in a shrug: an unexplained 87% reports the model's mood and hands the user all of the work. So the score resolves to exactly one verb before it reaches the screen, and each verb carries an obligation — an act must be overridable, a review must show its reasons, an ignore must admit it is below the line. Reversibility outranks confidence: if the action cannot be undone, the model does not get to auto-act at any score.

act

calibrate(history, options)

Pattern · Calibration & Track Record

"80% sure" only means something if, across every time it said 80%, it was right about 80% of the time. This computes the standard measures of that — Brier score, expected calibration error and the worst single bin — from a history of confidence/outcome pairs. Below thirty observations it refuses to render a verdict at all, because a track record you cannot support is just another confident claim.

SaidWas right
ECE
Brier
Samples

abstain(evidence, policy)

Pattern · Provenance, Citations & Honest Refusal

In a capital-allocation workflow one fluent, wrong claim costs more than a hundred refusals, so the default policy fails toward humility: every claim carries a source, two sources corroborate, and a clear majority agree before the system speaks. When it refuses, it returns what is missing — so the interface can name the gap instead of shrugging.

answerable

disclose(elapsedMs, options)

Pattern · Latency as a trust surface

Latency stops being a performance problem and becomes a trust problem at the moment silence becomes indistinguishable from failure. Under about 200ms a spinner reads as a flicker, so show nothing. Inside the budget, show the shape of the answer. Past the budget the user is owed a sentence, not another rotation.

skeleton

Decisions, on the record

What overrides what

The module encodes a precedence — when two rules disagree, one is written to win. That order is the design; the functions just enforce it.

Reversibility > confidence
If the action cannot be undone, the model does not get to auto-act at any score — decide() demotes act to review.
Insufficient data > a flattering number
Below thirty observations calibrate() refuses a verdict — a track record you cannot support is just another confident claim.
Humility > fluency
abstain() defaults fail toward refusal: every claim carries a source, two corroborate, a clear majority agree — or the system says what is missing instead of speaking.
A sentence > another rotation
Past the latency budget disclose() owes the user words, not a spinner — silence indistinguishable from failure is a trust cost, not a performance one.

Key decisions, and what each one buys

Zero dependencies
One standard ES module, no framework, no install step. Nothing here can rot when someone else publishes a breaking major.
No UI shipped
The visual layer is deliberately left to you — the verb matters more than the chip it ships in.
Thresholds are options, not constants
Where each band starts is a product decision someone owns and revisits — the code only enforces that the decision exists.
Violations, not suggestions
decide() reports review-without-reasons as a violation. A principle has no failing state; a function does.
Tests live on the page
The suite runs in this browser tab and under Node in CI, exiting non-zero on failure — what you read is what the function returns.

Proof

The tests, running now, in your browser

The same file runs under Node with node lab/loop.test.js and exits non-zero on failure. There is no test framework, because adding a dependency to a zero-dependency library would be the joke telling itself — the runner is about forty lines at the top of the file.

Two kinds of proof, kept separate on purpose. Implementation proof is below: the assertions, in this tab and under Node in CI on every push. Product proof lives elsewhere — each of these patterns ran in a production product, documented in the pattern library and the case studies. Passing tests prove the rules hold; the case studies argue the rules are worth holding.

running
  • Enable JavaScript to run the suite, or read the assertions in the source.

Why this exists

The strongest objection, kept in

“It’s a hundred lines of if-statements.” Exactly — and that is the entire point. The same decisions usually live in a slide deck where they can only be believed or ignored. Here they are versioned, testable, and able to fail loudly. The day a threshold is wrong, a test goes red — a deck has never once done that.

A rule you can run beats a rule you can argue with

Every one of these decisions used to be a slide. "Don't show a naked confidence score." "Fail toward humility." "Reversibility outranks confidence." Reasonable people nodded, and then shipped the naked number anyway, because a principle has no failing state.

Written as functions, the same rules get a failing state. decide() reports review-without-reasons as a violation rather than a suggestion. calibrate() returns insufficient-data instead of a flattering number. That is the whole argument for this file existing: human-in-the-loop design is a set of decisions, and decisions can be tested.

The eight patterns these come from are documented, with live demos, in the pattern library — and each one is running in a production product. This is their logic, extracted and made checkable.