01 Twin runtime
- Status
- direction
- Conceptual layer
- ③ Analytics state estimation on the Plant Box
- Repo layer
- L3
intelligence-core(stamped-l3-twin) - Source
- architecture section 3.6.8, section 5.1 PlantState · ADR 033 · placement D3 · Index: README.
1. What the twin is#
A twin is a model of one real asset, kept in step with that asset's live data, feeding a named decision. Stamped's twins are grey-box digital shadows:
- Physics gives structure (heat balance, the heater coil as a queue of billets, quench, ageing).
- An estimator keeps it in step with noisy sensors and states its uncertainty (Kalman level first; unscented Kalman filter once a step test identifies the plant; MHE only if it wins the offline benchmark, D19 DECIDED).
- Monte Carlo carries uncertainty in physical parameters (hundreds of draws per heat-treatment basket).
- A decision layer turns state and risk into alerts, actions, signals or write requests, only under accepted procedures.
| Twin (forging sector pack) | Data in | State | Outputs |
|---|---|---|---|
| Induction billet heater | 1 s exit temperature, push times, part, window | Level ± spread (v1); temperature of each billet in the coil (v2); pyrometer offset (v3) | Exit forecast for the coil under hold, step down, step up |
| Flow | Pushes, press parts, counters | Plant state, billets waiting, stop clock | Stop and restart events, waiting count, rhythm breaks |
| Heat treatment | 60 s zones, quench tank, gas | Inferred baskets, metal lag, tank temperature, ageing clocks | Zone and ageing alerts, water forecast, per-basket risk |
| Per-part record | Heater and flow outputs | Billet-to-part links, one loss cause per push | Part flags, loss ledger |
The heater twin becomes a full twin (it writes) only at AL2Autonomy levels (direction; fast-loop stages 1–3 = AL1–AL3) or AL3. The heat-treatment twin always stays a shadow.
2. Module layout#
New package stamped_l3_core/twin/ in intelligence-core:
stamped_l3_core/twin/
kit/ platform: kalman, ukf, gate, montecarlo, calibration (moving horizon),
replay (leave-one-run-out), drift, modelcard and parameter-row loader
assets/ sector pack physics: induction_heater (v1-v3), flow, ht_furnace, quench,
qfa (quench factor), ageing (Arrhenius)
genealogy/ billets (one cause per push), parts (billet-to-part link, flags), baskets
procedures/ runner (accepted procedures), budget (client view), write_request
runtime/ engine (per-second loop), plant_state, checkpoint, io, health
Not in the twin: reading the PLC (L1), storing records (L2), choosing who gets a card (L4), sending messages (L5).
3. The per-second loop#
- Read the latest readings from the plant broker (PLC time and edge receive time).
- Check freshness (under 5 s), physical bounds and frozen values; otherwise mark the affected state uncertain.
- Load the parameter row for the current context (06).
- Predict with physics; update the estimator through the gate (on each push, or each minute for heat treatment).
- Derive forecasts, risks, waiting counts and stop forecasts; publish PlantState direction on
twin/state/{line}. - Run accepted procedures; unaccepted ones run in shadow and are only logged.
- Checkpoint after every push; send records to L2.
Event time and wall time are separate. Physics and records use event time. A message goes out only if its event is younger than the procedure's freshness window (30 s for signals, 5 min for alerts). Backlogged data updates state but never triggers messages.
4. Plant states#
| State | Entered when | Allowed |
|---|---|---|
| Running | Pushes and parts at the part's rhythm | All accepted procedures |
| Stopped | No part or no push for 60 s | Hold and stop/start procedures |
| Changeover | Part changes | Changeover and first-part procedures; new parameters loaded |
| Restart | First pushes after a stop | First-part flags; no writes until the coil has been flushed |
| Unknown | Data gap, conflicting signals, startup | Part-independent alerts only; no writes, no signals |
5. Recovery#
| Fault | Response |
|---|---|
| Crash or reboot | Restore the last checkpoint. Gap under 3 min: resume with widened uncertainty. Longer: state uncertain until the coil is flushed |
| Data gap | State uncertain; the writer loses the heartbeat and restores the operator's values |
| Buffer replay after an outage | Update state; send no messages; mark the period as replayed |
| Frozen tag | Treat as missing; data-quality alert to the Stamped admin |
| Sensor misread | Physical floor plus a 4-sigma gate; after 3 rejections accept the next to avoid lockout |
| Clock jump over 2 s | Flag the window; the writer stops (02) |
| Unknown part | Learning mode (06) |
| Model drift | Refit; a write-enabled tag returns to AL2 |
The twin runs under a supervisor with automatic restart and an internal watchdog that exits when no tick happens for 5 s. It publishes a heartbeat every second to the writer. The loop has no hidden wall-clock dependency, so every incident replays from L2 through the same code.
6. Model governance#
- Every model has a card: asset, boundary, measured output, decision, owner, hard stops, version.
- Parameters are versioned rows with source, date, data volume and confidence.
- A new estimator replaces the current one only if it wins the same leave-one-run-out replay on error, alarm precision and recall, and interval coverage.
- Uncertainty must be honest (stated 90% intervals cover about 90%) before any standing write uses it.
- Weekly drift monitoring with a refit trigger.
- Model sophistication follows evidence: a richer model waits for the data that identifies it.
7. Performance and language#
The heaviest per-second work (a 14-billet heat balance, an estimator update, flow events) costs microseconds to a millisecond in NumPy; Monte Carlo per basket runs once a minute in milliseconds. The delay that matters is I/O, which no language changes. The twin stays in Python on the Plant BoxPlant-side computer for the fast loop (direction; D4) (D3), reusing the analysis code and matching the rest of L3. If one routine exceeds 100 ms, only that routine moves to Numba or a Rust extension. The plant image is compiled with Nuitka.
8. Deployables and boundaries#
| Deployable | Command | Runs |
|---|---|---|
| Scheduled L3 (existing) | stamped-l3-core | Cloud or plant server |
| Twin runtime (new) | stamped-l3-twin | Plant Box (cloud while messages only) |
Import-linter rules: the scheduler, engines and agentic code never import the twin's write path; the twin never imports the scheduler, agentic, fine-tune or challenger code. The plant image installs only the twin extra. Anything that becomes a card still goes out as EvidenceLayer contract for detector output (direction; as built: Finding finding.json 1.2.0) (direction; as built: FindingAs-built L3 detector output admitted to L4 (finding.json 1.2.0) 1.2.0) through the outbox.
9. Packs#
| Lives in | Holds |
|---|---|
Platform (twin/kit/) | Estimators, gate, Monte Carlo, calibration, replay, drift, loaders |
| Forging sector pack | Asset physics, default priors per alloy family, procedure templates |
| Site pack | Tag bindings, model cards per asset, parameter rows, part aliases, written limits, roster roles, allow-listed tags |
Plant names never appear in platform or sector pack code.
10. Reference tests#
Before rewritten modules replace analysis scripts, they reproduce the scripts' outputs on recorded data within stated tolerances: forecast error, ledger totals, link rates, flag counts, basket counts and physics self-checks. Plant data stays in the private site workspace; product repos hold expected numbers and synthetic fixtures of the same shape.
Page history: last 2 changes
- docs(technical): rewrite fast-loop/; all architecture diagrams in house style
7330f47 - docs(decisions): add ADR-033..038 (twin runtime, fast read path, plant-side writer, message classes, alerts and quality-to-lot link, part-keyed parameters), fast-loop technical set, rebuilt index with renumbering map; fix bare-number link text and ranges
22e2872