diff --git a/examples/kineticos-continuation-fleet/ARCHITECTURE.md b/examples/kineticos-continuation-fleet/ARCHITECTURE.md
new file mode 100644
index 0000000000..8db99a4565
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/ARCHITECTURE.md
@@ -0,0 +1,154 @@
+# KineticOS — Architecture (review map)
+
+This document maps the execution plan (Phases 0–9) onto the actual files, so a
+reviewer can navigate the scaffold quickly. The build is a **single Next.js 15
+app** (the proven shape for these hackathons) rather than the plan's literal
+multi-service monorepo — but every plane and gate from the plan is present as a
+module, and [`infra/render/render.yaml`](infra/render/render.yaml) shows how the
+single app fans out into the plan's separate Render services for production.
+
+## Vision (v2)
+
+> CAD image of an assembly with a broken component → modified CAD file out, so
+> production keeps running temporarily.
+
+Input is a CAD image (a render/screenshot of the assembly with a broken or
+missing component visible in situ). Output is a `ContinuationCadOutput` — a v1
+OpenSCAD-style CAD file, a small BOM, and a textual operator runbook that
+together let the line keep moving until the proper OEM replacement arrives.
+
+The system chooses between four continuation strategies:
+
+| Strategy | When |
+|---|---|
+| `substitute_component` | An off-the-shelf or community-cataloged part fits the surrounding assembly (Track A hit). |
+| `printed_insert` | The default for unique geometry: a 3D-printable substitute that respects the assembly's mating interfaces. |
+| `bridge_adapter` | A small printed adapter so an available in-stock standard fastener can stand in. |
+| `simplified_geometry` | Strip non-load features so in-house tooling can produce the part today. |
+
+## The load-bearing split
+
+> **Render runs things. Superplane decides when and whether things run.**
+
+| Plane | Module | Responsibility |
+|---|---|---|
+| Perception | `src/agents/perception/**` | CAD image → broken-component class → undamaged intent → mating interfaces |
+| Continuation Design | `src/agents/design/**` | strategy choice → sourced substitute OR generated insert/adapter |
+| Printability | `src/agents/material-adapter.ts` | re-parameterize against locally-loaded stock; printability margin |
+| Orchestration | `src/lib/superplane/client.ts` + `src/lib/gates/` + `src/worker/jobs.ts` | workflow state, gates, fan-in, audit |
+| Per-Job Runtime | `src/lib/render/client.ts` | births/kills the dedicated ephemeral `cad-validator-{jobId}` service |
+| CAD Output | `src/agents/fabrication.ts` | emits the v1 `ContinuationCadOutput` + runs canary/bulk validator |
+| Edge / Machine | `src/lib/edge/client.ts` | simulated on-prem agent: inventory + validator telemetry |
+
+Both sponsor integrations are **clean stubs that run at zero credentials** and
+upgrade to real APIs behind a flag (`SUPERPLANE_API_KEY`, `RENDER_API_KEY`).
+
+## How Render is used (4 ways)
+
+1. **Web service `kineticos`** — declared in [`render.yaml`](infra/render/render.yaml).
+2. **Per-job ephemeral `cad-validator-{jobId}`** — created at runtime by
+ [`src/lib/render/client.ts → provisionRuntime`](src/lib/render/client.ts);
+ deleted on terminal status by `teardownRuntime`. The orphan-reaper cron
+ (commented in `render.yaml`) reconciles via `listEphemeralServices`.
+3. **`/healthz`** — Render's healthcheck probe target
+ ([`src/app/healthz/route.ts`](src/app/healthz/route.ts)).
+4. **Managed Postgres `kineticos-pg`** — `DATABASE_URL` injected from the
+ managed DB into the web service.
+
+## How Superplane is used (4 ways)
+
+1. **`startRun`** — a Superplane run is opened on job ingest; the run id and
+ canvas URL are stamped on `Job.audit.superplaneRunId`.
+2. **`emit`** — every JobStatus transition is emitted as a step event, giving
+ the run record a live timeline of the pipeline.
+3. **`recordGate`** — every gate decision (proceed / human_review / block) is
+ recorded into the run record alongside the local audit trail.
+4. **Workflow gating** — the four gates (`composite_confidence`,
+ `continuation_strategy`, `printability`, `output_acceptance`) live as pure
+ policy functions in [`src/lib/gates/`](src/lib/gates/) and the worker
+ pauses the job (`status: needs_human`, `pendingGate` set) when one routes
+ to a human.
+5. **The agent fleet (Canvas)** — [`infra/superplane/kineticos-fleet.canvas.yaml`](infra/superplane/kineticos-fleet.canvas.yaml)
+ is the entire pipeline as a 34-node SuperPlane Canvas: the six perception
+ agents fan out in parallel from the ingest webhook, a `merge` fuses them, the
+ two design tracks branch on a sourcing hit, and each phase ends in the
+ `http evaluate-gate → if → approval` gate pattern before the line resumes.
+ Every action node is an `http` executor hitting one agent endpoint —
+ granular [`/api/agents/[agent]`](src/app/api/agents) for the perception
+ sensors + design tracks, coarse [`/api/stages/*`](src/app/api/stages) for
+ phases 4 and 5–7. The in-process worker and the Canvas are two drivers of the
+ same agents + gates (`LOCAL_ORCHESTRATOR=1` selects the worker).
+
+## The spine
+
+[`src/lib/types.ts`](src/lib/types.ts) — the canonical `Job` object every stage
+keys off (Phase 0.3), plus the Phase 2/3/4/8 output contracts, the gate types,
+the new `ContinuationStrategy` enum, the v1 `ContinuationCadOutput` type, and
+the **locked stage signatures** the worker imports and each stage module
+implements.
+
+## The pipeline (worker drives it, upserting the Job after every step)
+
+`src/worker/jobs.ts` runs stages in order and evaluates a Superplane gate
+between phases; a human-scoped gate pauses the job (`needs_human`) and resumes
+on `resolveGate()`.
+
+| Phase | Stage | File |
+|---|---|---|
+| 2A | CAD image conditioning / admissibility | `src/agents/perception/conditioning.ts` |
+| 2B | Broken-component localization *(exemplar)* | `src/agents/perception/classification.ts` |
+| 2C | Reconstruct the **undamaged intent** | `src/agents/perception/reconstruction.ts` |
+| 2D | Interface extraction (terminal scale chain) | `src/agents/perception/dimensioning.ts` |
+| 2E | Material & surface inference | `src/agents/perception/material.ts` |
+| 2F | Telemetry track + sensor fusion | `src/agents/perception/telemetry.ts` |
+| **2.G** | **Composite confidence gate** | `src/lib/gates/index.ts` → `compositeConfidenceGate` |
+| 3A | Substitute sourcing (Track A) | `src/agents/design/sourcing.ts` |
+| 3B | Generative continuation CAD B1–B8 (Track B) | `src/agents/design/generative-cad.ts` |
+| **3.G** | **Continuation strategy gate** | `src/lib/gates/index.ts` → `designAcceptanceGate` |
+| 4 | Printability adaptation + toolpath | `src/agents/material-adapter.ts` |
+| **4.3** | **Printability feasibility gate** | `src/lib/gates/index.ts` → `structuralGate` |
+| 5 | Provision dedicated cad-validator service | `src/lib/render/client.ts` → `provisionRuntime` |
+| 6/7 | Emit v1 ContinuationCadOutput + canary/bulk validator | `src/agents/fabrication.ts` |
+| 8 | Output validation | `src/agents/fabrication.ts` |
+| **8.1** | **Output acceptance gate** → COMPLETE | `src/lib/gates/index.ts` → `acceptanceGate` |
+| 8.2 | Teardown dedicated runtime | `src/lib/render/client.ts` → `teardownRuntime` |
+| 8.3 | Audit seal | `src/lib/audit.ts` + `Job.audit` |
+
+## Data & API
+
+- `src/lib/store.ts` — dual-layer job store: globalThis `Map` always-on, Postgres
+ write-through when `DATABASE_URL` is set. `src/lib/db/**` is the pg layer.
+- `src/app/api/jobs/route.ts` — `POST` ingest (422 if neither `cadImageUris`
+ nor `telemetryUri`), `GET` list.
+- `src/app/api/jobs/[id]/route.ts` — `GET` one (polling target).
+- `src/app/api/jobs/[id]/gate/route.ts` — `POST` resolve a scoped human gate.
+- `src/app/healthz/route.ts` — Render health check target.
+
+## UI (live polling, dark theme)
+
+`src/app/page.tsx` tab shell → `src/components/tabs/{IntakeTab,PipelineTab,GatesTab,AuditTab}.tsx`,
+all built on `src/components/ui.tsx` primitives and `src/lib/usePolling.ts`.
+The PipelineTab carries the new **Continuation CAD output (v1)** panel that
+renders the OpenSCAD payload + BOM + runbook with a `data:` download link.
+
+## V1 CAD output — what's intentionally rough
+
+The v1 `ContinuationCadOutput` payload is a plain-text OpenSCAD script. It
+captures the resolved mating interfaces (bore, plate footprint, tooth count)
+but the non-load features are simplified for fast 3D printing — gear teeth are
+square notches, not involute curves; brackets are flat plates with corner
+holes; everything else is a bored block. A v2 swap to a real CAD-kernel
+B-rep through the `cad-validator-{jobId}` service is a drop-in replacement of
+the `buildOpenscadScript` helper in `src/agents/fabrication.ts` — the
+`ContinuationCadOutput` contract (`format`, `filename`, `contents`, `bom`,
+`runbook`) is the same, and every other plane already keys off that shape.
+
+## Not yet built (Phase 9 hardening — deliberately deferred for review)
+
+- Orphan-reaper cron logic (the primitive `render.listEphemeralServices()`
+ exists; the reconcile job/endpoint is a TODO in `render.yaml`).
+- 2A→3A bounded reconstruction refinement loop (single-pass today).
+- Real per-job mTLS / signing-key verification on the edge channel (key is
+ generated and passed; verification is simulated).
+- Real CAD kernel for the v2 continuation output (drop-in behind
+ `buildOpenscadScript`).
diff --git a/examples/kineticos-continuation-fleet/README.md b/examples/kineticos-continuation-fleet/README.md
new file mode 100644
index 0000000000..9aceb6bbec
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/README.md
@@ -0,0 +1,140 @@
+# KineticOS
+
+**CAD image of an assembly with a broken component in → modified CAD file out, so production keeps running.**
+
+
+
+
+
+
+
+> ☝️ Opens the **SuperPlane app** — the canvas with the full agent fleet (every
+> perception, design, printability, and validation agent + the gates between
+> them), hosted locally on **http://localhost:3001** (KineticOS keeps :3000).
+> To bring it up from scratch — no Docker Desktop needed:
+>
+> ```bash
+> # 1. a headless container runtime
+> brew install colima docker && colima start --vm-type vz --vz-rosetta
+> # 2. host the SuperPlane app on :3001
+> docker run -d --name superplane -p 3001:3000 \
+> -e BASE_URL=http://localhost:3001 -e ALLOWED_WS_ORIGINS=http://localhost:3001 \
+> -v spdata:/app/data ghcr.io/superplanehq/superplane-demo:stable
+> # 3. point the fleet canvas at KineticOS and load it — host.docker.internal
+> # reaches the host from inside the SuperPlane container
+> infra/superplane/apply.sh http://host.docker.internal:3000 # or import the YAML in the UI
+> ```
+>
+> See [`infra/superplane/`](infra/superplane/) for the canvas + the full walkthrough.
+
+KineticOS ingests a CAD image of a mechanical assembly that has a broken or
+missing component, locates the break, **reconstructs the intended undamaged
+geometry** (never the cracked artifact), picks a **continuation strategy** —
+an off-the-shelf substitute, a 3D-printable insert, a small bridge adapter, or
+a simplified-geometry version — re-parameterizes the design against locally
+loaded stock, then spins up a **dedicated, ephemeral cloud validator per job**
+and hands back a **v1 CAD file** the operator can save, slice, and use to keep
+the line running until the OEM replacement arrives.
+
+> The CAD output itself is **v1** — an OpenSCAD-style script that captures the
+> mating interfaces and the chosen strategy. The surrounding system (gates,
+> per-job ephemeral runtime, audit trail, multi-plane orchestration) is fit to
+> the final vision so v1 can be swapped for a real CAD-kernel output later
+> without disturbing anything else.
+
+Two load-bearing constraints, kept strictly separate:
+
+> **Render runs things. Superplane decides when and whether things run.**
+
+## Render — four ways
+
+1. **Web service** — `kineticos` hosts the Next.js app (UI + API + `/healthz`).
+ See [`infra/render/render.yaml`](infra/render/render.yaml).
+2. **Per-job ephemeral service** — `cad-validator-{jobId}` is born and killed
+ by [`src/lib/render/client.ts`](src/lib/render/client.ts) for every job.
+ Receives the v1 CAD output, runs the canary→bulk validator passes, streams
+ progress frames back. No shared multi-tenant endpoint — one job, one service.
+3. **Healthcheck probe** — Render polls [`/healthz`](src/app/healthz/route.ts)
+ to drive its readiness gate.
+4. **Managed Postgres** — `kineticos-pg` backs the durable Job + audit store
+ when `DATABASE_URL` is set ([`src/lib/db/**`](src/lib/db/)).
+
+## Superplane — four ways
+
+1. **Workflow run record** — every Job starts a Superplane run; the canvas
+ URL is stamped on `Job.audit.superplaneRunId`
+ ([`src/lib/superplane/client.ts`](src/lib/superplane/client.ts) ·
+ `startRun`).
+2. **Step transitions** — every status change is emitted as a step event
+ (`emit`) so the run record is a live timeline of the pipeline.
+3. **Gates** — four distinct gates (`composite_confidence` 2.G,
+ `continuation_strategy` 3.G, `printability` 4.3, `output_acceptance` 8.1)
+ live as pure policy functions in [`src/lib/gates/`](src/lib/gates/) and
+ recorded through `recordGate`. Human-scoped gates park the job in the
+ **Gates** tab; the rest pass through.
+4. **Audit fan-in** — gate decisions are mirrored into the run record, giving
+ one immutable provenance trail across every plane — the liability record
+ the AuditTab renders.
+
+### The agent fleet (Canvas)
+
+[`infra/superplane/kineticos-fleet.canvas.yaml`](infra/superplane/kineticos-fleet.canvas.yaml)
+is the **whole process as one SuperPlane Canvas** — 34 nodes that fan the six
+perception agents out in parallel, fuse them, branch the two design tracks, and
+gate between every phase (`http evaluate-gate → if → approval`) before the line
+resumes production. Each action node is an `http` executor calling one KineticOS
+agent at [`/api/agents/*`](src/app/api/agents) (roster: `GET /api/agents`); it
+runs at zero credentials. See [`infra/superplane/README.md`](infra/superplane/README.md)
+to load it, and `infra/superplane/smoke.sh` to rehearse a full run offline.
+
+## Runs at zero credentials
+
+Every integration is optional. With **no** environment variables the full
+pipeline runs end-to-end on deterministic fallbacks: Claude → rule-based
+stages, Superplane → an in-process control-plane model, Render → a simulated
+provision/teardown lifecycle, the validator → simulated telemetry, Postgres →
+an in-process `Map`. Each key upgrades exactly one plane from simulated to
+real.
+
+## Quickstart
+
+```bash
+npm install
+npm run dev # http://localhost:3000
+```
+
+Open the app → **Intake** tab → drop a CAD image of the broken assembly → add
+an optional assembly context / failure note → **Ingest CAD image**, then watch
+the **Pipeline** tab drive the job through perception → continuation design →
+printability → provisioning → CAD output → validation, with Superplane gates
+in between. The **Continuation CAD output (v1)** panel shows the emitted
+OpenSCAD file with a download link. Low-confidence jobs pause in the
+**Gates** tab; the full provenance trail is in **Audit**.
+
+Optional — turn planes real:
+
+```bash
+cp .env.example .env.local
+# ANTHROPIC_API_KEY → Claude for the LLM-bearing stages
+# SUPERPLANE_API_KEY → the real Superplane control plane
+# RENDER_API_KEY → real ephemeral per-job cad-validator provisioning
+# DATABASE_URL → durable jobs + audit trail (npm run db:migrate && npm run db:seed)
+```
+
+## Architecture
+
+The single Next.js app maps the plan's four planes and every gate onto modules;
+see **[ARCHITECTURE.md](ARCHITECTURE.md)** for the full Phase-0–9 → file map.
+The canonical `Job` object in [`src/lib/types.ts`](src/lib/types.ts) is the
+spine everything keys off — including the new `ContinuationStrategy` enum and
+the `ContinuationCadOutput` v1 deliverable.
+[`infra/render/render.yaml`](infra/render/render.yaml) shows the production
+Render split.
+
+## Status
+
+Scaffold + architecture, aligned to the CAD-continuation vision. The pipeline,
+gates, dual-layer store, Render/Superplane stub interfaces, live UI, and v1
+CAD output emitter run end-to-end. The v1 CAD output is intentionally a
+placeholder (OpenSCAD script) — the surrounding system is built to accept a
+real CAD-kernel STEP/STL output without changing any other module.
diff --git a/examples/kineticos-continuation-fleet/SUBMISSION.md b/examples/kineticos-continuation-fleet/SUBMISSION.md
new file mode 100644
index 0000000000..4a549a3a6b
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/SUBMISSION.md
@@ -0,0 +1,62 @@
+# KineticOS — a SuperPlane agent-fleet example
+
+A self-contained snapshot of **KineticOS**, a project built **on top of
+SuperPlane**: a CAD image of a broken industrial machine comes in → a fleet of
+agents looks at it, identifies the fault, reconstructs the intended part,
+generates a new **3D-printable CAD file**, validates it, and **resumes
+production** — with SuperPlane gating every phase.
+
+> **Heads-up for maintainers:** this is a *downstream application* example, not a
+> change to the SuperPlane platform. It's contributed as an `examples/` folder so
+> it adds nothing to your build/CI and touches none of your tree. If you'd prefer
+> only the workflow template, the single file worth upstreaming is the Canvas
+> below — it can be dropped into `templates/canvases/` with `metadata.isTemplate:
+> true` per `docs/contributing/templates.md`. Happy to reshape this PR to just
+> that if you'd rather.
+
+## The primary artifact — the Canvas
+
+[`infra/superplane/kineticos-fleet.canvas.yaml`](infra/superplane/kineticos-fleet.canvas.yaml)
+is a 34-node Canvas exercising a lot of SuperPlane in one real workflow:
+
+- **Parallel fan-out + `merge`** — six perception agents run concurrently from
+ the ingest trigger, then a `merge` barrier fuses them.
+- **The gate pattern (×4)** — each phase ends in `http` (evaluate-gate) → `if`
+ (`decision == "proceed"`) → `approval` (the human gate), with rejections
+ routed to a halt sink.
+- **Conditional branching** — an `if` picks an off-the-shelf substitute vs. a
+ generated continuation part.
+- **`http` executors** call the app's agent endpoints; **annotations** document
+ each section on-canvas.
+
+`infra/superplane/kineticos-fleet.local.canvas.yaml` is the same Canvas with
+executor URLs pointed at `host.docker.internal:3000` for a locally-hosted
+SuperPlane (container) calling the app (host).
+
+## The rest of the folder (the downstream app)
+
+- `src/app/api/agents/**` + `src/lib/contract.ts` — the Next.js HTTP executors
+ each Canvas node calls (one agent per endpoint). **Illustrative** — this is
+ the KineticOS app, not SuperPlane platform code; it isn't built by your CI.
+- `infra/superplane/{README.md,apply.sh,smoke.sh}` — node→agent→endpoint map,
+ a canvas loader (host substitution + `superplane canvas create`), and an
+ offline end-to-end fleet rehearsal.
+- `README.md` / `ARCHITECTURE.md` — the KineticOS project docs for context.
+
+## Run it
+
+```bash
+# the app (executors) — runs at zero credentials
+npm install && npm run dev # KineticOS on :3000
+
+# SuperPlane locally (no Docker Desktop needed)
+brew install colima docker && colima start --vm-type vz --vz-rosetta
+docker run -d --name superplane -p 3001:3000 \
+ -e BASE_URL=http://localhost:3001 -e ALLOWED_WS_ORIGINS=http://localhost:3001 \
+ -v spdata:/app/data ghcr.io/superplanehq/superplane-demo:stable
+
+# load the fleet (host.docker.internal reaches the host from the container)
+infra/superplane/apply.sh http://host.docker.internal:3000 # then import in the UI
+```
+
+Full project: https://github.com/sahielbose/KineticOS-Superplane-Hackathon
diff --git a/examples/kineticos-continuation-fleet/infra/superplane/README.md b/examples/kineticos-continuation-fleet/infra/superplane/README.md
new file mode 100644
index 0000000000..caed345f6c
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/infra/superplane/README.md
@@ -0,0 +1,175 @@
+# KineticOS — the SuperPlane agent fleet
+
+> **A CAD image of a broken industrial machine comes in → a new, 3D-printable
+> CAD file goes out, so the line keeps running.**
+
+
+
+
+
+
+
+> SuperPlane is hosted locally on **:3001** (KineticOS keeps :3000). Bring it up
+> with Colima — no Docker Desktop needed — then load this fleet:
+> ```bash
+> brew install colima docker && colima start --vm-type vz --vz-rosetta
+> docker run -d --name superplane -p 3001:3000 \
+> -e BASE_URL=http://localhost:3001 -e ALLOWED_WS_ORIGINS=http://localhost:3001 \
+> -v spdata:/app/data ghcr.io/superplanehq/superplane-demo:stable
+> infra/superplane/apply.sh http://host.docker.internal:3000 # SuperPlane-in-container → KineticOS-on-host
+> ```
+
+This directory holds the **agent fleet as a SuperPlane Canvas** —
+[`kineticos-fleet.canvas.yaml`](kineticos-fleet.canvas.yaml) — that orchestrates
+the *exact* KineticOS process end to end:
+
+```
+look at the machine → identify what's wrong → reconstruct the intended part
+ → generate a new 3D-printable CAD file → prove it holds → resume production
+```
+
+SuperPlane is the **brain**: it decides *when and whether* each agent runs, fans
+the perception sensors out in parallel, branches the two design tracks, and parks
+the job at a **human gate** the moment confidence, the design trail, the
+printability margin, or the output validation falls short. The compute itself
+runs in the KineticOS Next.js app (and Render's per-job validator); SuperPlane
+never executes domain logic — it routes.
+
+Every `TYPE_ACTION` node is an **`http` executor** that POSTs `{ "job_id": … }`
+to one KineticOS endpoint. The whole fleet runs **at zero credentials**: each
+agent has a deterministic fallback, so you can demo the full canvas offline.
+
+---
+
+## The fleet at a glance
+
+`34 nodes` · `1 trigger` · `28 agent/gate/control nodes` · `5 doc widgets` · `40 edges`
+
+| # | Node | Component | Calls | What the agent does |
+|---|------|-----------|-------|---------------------|
+| — | Ingest: broken-machine CAD image | `webhook` | *(event source)* | Fires when `POST /api/jobs` posts to `SP_INGEST_WEBHOOK` |
+| 2A | Conditioning | `http` | `/api/agents/conditioning` | Is the CAD image admissible (sharp, exposed, on-part)? |
+| 2B | Classification | `http` | `/api/agents/classification` | **Which** component is broken (drives everything downstream) |
+| 2C | Reconstruction | `http` | `/api/agents/reconstruction` | Rebuild the *intended undamaged* geometry — needs 2B |
+| 2D | Dimensioning | `http` | `/api/agents/dimensioning` | Mating interfaces + a terminating scale chain |
+| 2E | Material inference | `http` | `/api/agents/material-infer` | Material class + surface finish |
+| 2F | Telemetry fusion | `http` | `/api/agents/telemetry` | Failure mode from telemetry + sensor-fusion agreement |
+| — | Fuse the perception sensors | `merge` | — | Barrier: wait for all sensors |
+| 2.x | Assemble PerceptionResult | `http` | `/api/agents/perception-assemble` | Compose the composite-confidence score |
+| **2.G** | **Gate · composite confidence** | `http` + `if` + `approval` | `/api/evaluate-gate` | Proceed, or scope a human to the weak field |
+| 3A | Sourcing | `http` | `/api/agents/sourcing` | Hunt an off-the-shelf / community substitute |
+| 3B | Generative CAD | `http` | `/api/agents/generative-cad` | Synthesise the continuation insert via the **B1–B8** trail |
+| **3.G** | **Gate · continuation strategy** | `http` + `if` + `approval` | `/api/evaluate-gate` | Generated + load-bearing → human review |
+| 4 | Printability adaptation | `http` | `/api/stages/material` | Re-parameterize to locally-loaded stock |
+| **4.3** | **Gate · printability feasibility** | `http` + `if` + `approval` | `/api/evaluate-gate` | Margin below duty load → block / relax |
+| 5–7 | Provision + emit + validate | `http` | `/api/stages/fabricate-report` | Birth the ephemeral Render `cad-validator-{jobId}`, emit the **v1 CAD output**, run canary→bulk |
+| **8.1** | **Gate · output acceptance** | `http` + `if` + `approval` | `/api/evaluate-gate` | Fan-in of dimensional + structural + anomaly checks |
+| 8.3 | Seal run + resume production | `http` | `/api/agents/finalize` | Seal the audit trail; mark the line resumed |
+| — | ✅ Line resumed / ⛔ Halted | `noop` | — | Terminal sinks |
+
+The fleet roster is also live at **`GET /api/agents`**.
+
+### The gate pattern (used ×4)
+
+Every phase ends in the same three-node shape:
+
+```
+ http evaluate-gate ──► if (decision == "proceed") ──true──► next phase
+ │
+ false
+ ▼
+ approval (human gate) ──approved──► next phase
+ │
+ rejected
+ ▼
+ ⛔ Halt
+```
+
+The gate policy itself lives once, in
+[`src/lib/gates/index.ts`](../../src/lib/gates/index.ts); `/api/evaluate-gate`
+returns `{ decision, reason, scoped_field }`. When a gate routes to a human it
+names the **exact** weak field (the component class, the reconstruction overlay,
+one caliper reading) — never a generic "approve?". The operator resolves it
+through the app's existing `POST /api/jobs/{id}/gate` endpoint / **Gates** tab.
+
+---
+
+## Run it
+
+### 0. Start KineticOS (the executors)
+
+```bash
+npm install && npm run dev # http://localhost:3000 — runs at zero credentials
+```
+
+Smoke-test the fleet endpoints directly (what the canvas nodes do, in order):
+
+```bash
+infra/superplane/smoke.sh http://localhost:3000
+```
+
+### 1. Point the canvas at your KineticOS base URL
+
+The YAML ships with `https://kineticos.onrender.com`. To retarget (e.g. local):
+
+```bash
+# writes a substituted copy to /tmp and (optionally) applies it.
+# SuperPlane runs in a container, so it reaches KineticOS (host :3000) via
+# host.docker.internal — use plain localhost:3000 only if SP runs on the host.
+infra/superplane/apply.sh http://host.docker.internal:3000
+```
+
+### 2. Load the canvas into SuperPlane
+
+**CLI** (preferred — needs the `superplane` CLI authenticated to your org):
+
+```bash
+superplane canvas create -f infra/superplane/kineticos-fleet.canvas.yaml
+# or let apply.sh do the host-substitution + create in one step:
+APPLY=1 infra/superplane/apply.sh https://your-kineticos.example.com
+```
+
+**UI:** open SuperPlane → **New canvas → Import YAML** → paste the (substituted)
+file. The graph, gates, and annotations render exactly as laid out here.
+
+### 3. Wire the ingest event source
+
+1. In SuperPlane, create a **Webhook event source** and bind it to the
+ **Ingest: broken-machine CAD image** trigger node. Copy its URL + signing token.
+2. In KineticOS, set:
+
+ ```bash
+ SP_INGEST_WEBHOOK=
+ SP_WEBHOOK_TOKEN=
+ # leave LOCAL_ORCHESTRATOR unset so intake fires the webhook instead of the
+ # in-process worker (see src/app/api/jobs/route.ts)
+ ```
+
+Now every `POST /api/jobs` (an operator dropping a CAD image in the **Intake**
+tab) fires the canvas, and SuperPlane drives the whole fleet, calling back into
+the agent endpoints and gating between phases.
+
+> **Note on the trigger node.** SuperPlane models inbound webhooks as *event
+> sources* that a trigger node binds to in the UI. The node here uses
+> `component: "webhook"` as a placeholder — if your SuperPlane build names the
+> generic inbound trigger differently, set it on that one node; everything
+> downstream is unchanged.
+
+---
+
+## How this maps to the rest of KineticOS
+
+| Concern | Where |
+|---|---|
+| The agents (Claude + deterministic fallback) | [`src/agents/**`](../../src/agents) |
+| The four gate policies | [`src/lib/gates/index.ts`](../../src/lib/gates/index.ts) |
+| Granular per-agent executors | [`src/app/api/agents/[agent]/route.ts`](../../src/app/api/agents) |
+| Coarse phase executors (4, 5–7) | [`src/app/api/stages/**`](../../src/app/api/stages) |
+| Gate evaluator | [`src/app/api/evaluate-gate/route.ts`](../../src/app/api/evaluate-gate/route.ts) |
+| Wire contract (snake_case) | [`src/lib/contract.ts`](../../src/lib/contract.ts) |
+| In-process equivalent (offline / no canvas) | [`src/worker/jobs.ts`](../../src/worker/jobs.ts) |
+
+The in-process worker and this canvas are **two drivers of the same agents**:
+set `LOCAL_ORCHESTRATOR=1` to drive the pipeline in-process (no SuperPlane);
+leave it unset to let this fleet drive it. Same agents, same gates, same audit
+trail either way.
diff --git a/examples/kineticos-continuation-fleet/infra/superplane/apply.sh b/examples/kineticos-continuation-fleet/infra/superplane/apply.sh
new file mode 100755
index 0000000000..2b4336d4be
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/infra/superplane/apply.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+# Retarget the KineticOS fleet canvas at a KineticOS base URL and (optionally)
+# load it into SuperPlane.
+#
+# infra/superplane/apply.sh [BASE_URL]
+#
+# BASE_URL KineticOS base the http nodes call (default: the prod Render URL).
+# APPLY=1 also run `superplane canvas create -f` on the substituted file.
+#
+# Examples:
+# infra/superplane/apply.sh http://localhost:3000 # just substitute
+# APPLY=1 infra/superplane/apply.sh https://kos.example.com # substitute + create
+set -euo pipefail
+
+DEFAULT_HOST="https://kineticos.onrender.com"
+BASE_URL="${1:-$DEFAULT_HOST}"
+BASE_URL="${BASE_URL%/}" # strip any trailing slash
+
+SRC="$(cd "$(dirname "$0")" && pwd)/kineticos-fleet.canvas.yaml"
+OUT="${TMPDIR:-/tmp}/kineticos-fleet.canvas.yaml"
+
+if [[ ! -f "$SRC" ]]; then
+ echo "✗ canvas not found: $SRC" >&2
+ exit 1
+fi
+
+# Replace the baked-in host with the target base URL.
+sed "s#${DEFAULT_HOST}#${BASE_URL}#g" "$SRC" > "$OUT"
+COUNT="$(grep -c "${BASE_URL}/api/" "$OUT" || true)"
+echo "✓ wrote $OUT"
+echo " → ${COUNT} http executor URLs now point at ${BASE_URL}"
+
+if [[ "${APPLY:-0}" == "1" ]]; then
+ if ! command -v superplane >/dev/null 2>&1; then
+ echo "✗ APPLY=1 but the 'superplane' CLI is not installed / on PATH." >&2
+ echo " Install it, authenticate to your org, then re-run — or import $OUT in the UI." >&2
+ exit 1
+ fi
+ echo "→ superplane canvas create -f $OUT"
+ superplane canvas create -f "$OUT"
+ echo "✓ canvas created. Bind the ingest trigger to a Webhook event source,"
+ echo " then set SP_INGEST_WEBHOOK + SP_WEBHOOK_TOKEN in KineticOS."
+else
+ echo
+ echo "Next:"
+ echo " • CLI: superplane canvas create -f $OUT"
+ echo " • UI: New canvas → Import YAML → paste $OUT"
+ echo " • Then bind the ingest trigger to a Webhook event source and set"
+ echo " SP_INGEST_WEBHOOK + SP_WEBHOOK_TOKEN in KineticOS (see README.md)."
+fi
diff --git a/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.canvas.yaml b/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.canvas.yaml
new file mode 100644
index 0000000000..173ee221a0
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.canvas.yaml
@@ -0,0 +1,504 @@
+# ─────────────────────────────────────────────────────────────────────────
+# KineticOS — the agent fleet, as a SuperPlane Canvas.
+#
+# THE EXACT PROCESS, end to end:
+# a CAD image of an industrial machine with a broken component arrives →
+# a fleet of perception agents LOOKS at it and IDENTIFIES what is wrong →
+# a design fleet RECONSTRUCTS the intended geometry and GENERATES a new
+# 3D-printable CAD file → printability + validation agents prove it will
+# hold → and on acceptance the line RESUMES PRODUCTION on that v1 file
+# until the OEM replacement arrives.
+#
+# SuperPlane is the BRAIN: it decides *when and whether* each agent runs, and
+# it parks the job at a human gate whenever confidence, the design trail, the
+# printability margin, or the output validation says so. Every TYPE_ACTION node
+# below is an `http` executor that calls one KineticOS agent endpoint; the agent
+# itself (Claude-backed, with a deterministic fallback) lives in the Next.js app.
+#
+# Replace the host `https://kineticos.onrender.com` with your KineticOS base URL
+# (e.g. http://localhost:3000 for local dev) — infra/superplane/apply.sh does
+# this substitution for you. The ingest trigger binds to the SuperPlane webhook
+# event source that KineticOS fires as SP_INGEST_WEBHOOK on POST /api/jobs.
+# ─────────────────────────────────────────────────────────────────────────
+apiVersion: v1
+kind: Canvas
+metadata:
+ name: "KineticOS — broken-machine → 3D-printable continuation fleet"
+ description: "Looks at a CAD image of a broken industrial machine, identifies the fault, generates a new 3D-printable CAD file, and resumes production — gated by SuperPlane at every phase."
+ isTemplate: false
+spec:
+ nodes:
+ # ───────────────── ingest (event source) ─────────────────
+ - id: "ingest-trigger"
+ name: "Ingest: broken-machine CAD image"
+ type: "TYPE_TRIGGER"
+ configuration: {}
+ position: { x: 120, y: 760 }
+ component: "webhook"
+ isCollapsed: false
+
+ # ───────────────── PERCEPTION FLEET — "look + identify what's wrong" ─────────────────
+ - id: "perc-conditioning"
+ name: "2A · Conditioning (image admissibility)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/conditioning"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 160 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-classification"
+ name: "2B · Classification (locate the break)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/classification"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 420 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-reconstruction"
+ name: "2C · Reconstruction (undamaged intent)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/reconstruction"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 1040, y: 420 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-dimensioning"
+ name: "2D · Dimensioning (interfaces + scale)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/dimensioning"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 920 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-material"
+ name: "2E · Material inference"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/material-infer"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 1160 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-telemetry"
+ name: "2F · Telemetry fusion"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/telemetry"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 1400 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-merge"
+ name: "Fuse the perception sensors"
+ type: "TYPE_ACTION"
+ configuration:
+ enableStopIf: false
+ enableTimeout: true
+ executionTimeout: { unit: "minutes", value: 2 }
+ position: { x: 1480, y: 780 }
+ component: "merge"
+ isCollapsed: false
+ - id: "perc-assemble"
+ name: "Assemble PerceptionResult"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/perception-assemble"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 1920, y: 780 }
+ component: "http"
+ isCollapsed: false
+
+ # ───────────────── GATE 2.G · composite confidence ─────────────────
+ - id: "gate-2g"
+ name: "Gate 2.G · composite confidence"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "composite_confidence"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 2360, y: 780 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-2g"
+ name: "2.G clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 2800, y: 780 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-2g"
+ name: "Operator review · confirm the broken component"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 2800, y: 1080 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── DESIGN FLEET — "make a new 3D-printable CAD file" ─────────────────
+ - id: "design-sourcing"
+ name: "3A · Sourcing (off-the-shelf substitute)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/sourcing"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 3240, y: 780 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-sourced"
+ name: "Substitute found?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: "previous().data.body.matched == true"
+ position: { x: 3680, y: 780 }
+ component: "if"
+ isCollapsed: false
+ - id: "design-generative"
+ name: "3B · Generative CAD (B1–B8)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/generative-cad"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 45
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 3680, y: 1060 }
+ component: "http"
+ isCollapsed: false
+
+ # ───────────────── GATE 3.G · continuation strategy ─────────────────
+ - id: "gate-3g"
+ name: "Gate 3.G · continuation strategy"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "continuation_strategy"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 4120, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-3g"
+ name: "3.G clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 4560, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-3g"
+ name: "Operator review · the B1–B8 design trail"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 4560, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── PRINTABILITY + GATE 4.3 ─────────────────
+ - id: "phase4-material"
+ name: "4 · Printability adaptation (local stock)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/stages/material"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 5000, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "gate-43"
+ name: "Gate 4.3 · printability feasibility"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "printability"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 5440, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-43"
+ name: "4.3 clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 5880, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-43"
+ name: "Operator review · relax constraints or abort"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 5880, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── PROVISION + EMIT + VALIDATE (Render) + GATE 8.1 ─────────────────
+ - id: "phase5-fabricate"
+ name: "5–7 · Provision validator + emit CAD + validate"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/stages/fabricate-report"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 120
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 4, intervalSeconds: 10 }
+ position: { x: 6320, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "gate-81"
+ name: "Gate 8.1 · output acceptance"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "output_acceptance"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 6760, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-81"
+ name: "8.1 accepted?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 7200, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-81"
+ name: "Operator review · the v1 continuation output"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 7200, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── RESUME PRODUCTION ─────────────────
+ - id: "finalize"
+ name: "8.3 · Seal run + resume production"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "https://kineticos.onrender.com/api/agents/finalize"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 7640, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "notify-resumed"
+ name: "✅ Line resumed on v1 continuation"
+ type: "TYPE_ACTION"
+ configuration: {}
+ position: { x: 8080, y: 880 }
+ component: "noop"
+ isCollapsed: false
+ - id: "halt-aborted"
+ name: "⛔ Halted — operator aborted"
+ type: "TYPE_ACTION"
+ configuration: {}
+ position: { x: 5000, y: 1560 }
+ component: "noop"
+ isCollapsed: false
+
+ # ───────────────── annotations (documentation widgets) ─────────────────
+ - id: "anno-overview"
+ name: "overview"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "blue"
+ width: 470
+ height: 360
+ text: "# KineticOS agent fleet\n\n**A CAD image of a broken industrial machine comes in → a modified, 3D-printable CAD file goes out, so the line keeps running.**\n\nSuperPlane is the brain: it decides *when and whether* each agent runs and parks the job at a human gate when confidence is low. Render runs the compute (the per-job `cad-validator-{jobId}` service).\n\nEvery action node is an `http` executor calling one KineticOS agent at `/api/agents/*`. Runs end-to-end at **zero credentials** — each agent has a deterministic fallback."
+ position: { x: 110, y: 200 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-perception"
+ name: "anno-perception"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "yellow"
+ width: 520
+ height: 250
+ text: "## 1 · Look & identify what is wrong\n\nSix perception agents fan out **in parallel** from the ingest event:\n\n- **2A Conditioning** — is the image even usable?\n- **2B Classification** — *which* component is broken (drives everything downstream).\n- **2C Reconstruction** — rebuild the *intended, undamaged* shape (never the cracked artifact). Needs 2B.\n- **2D Dimensioning** — the mating interfaces + a terminating scale chain.\n- **2E Material** · **2F Telemetry** — material/finish + sensor-fusion cross-check.\n\nThe **Merge** node waits for all sensors, then **Assemble** fuses them into one composite-confidence score."
+ position: { x: 600, y: -220 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-gates"
+ name: "anno-gates"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "red"
+ width: 470
+ height: 230
+ text: "## The gate pattern (×4)\n\nEvery phase ends in the same shape:\n\n`http evaluate-gate` → **If `decision == \"proceed\"`** → next phase.\n\nOtherwise the job parks at an **Approval** node — the human gate. SuperPlane scopes it to the *exact* weak field (the class, the overlay, one caliper reading). **Approved** resumes the flow; **Rejected** routes to **Halt**.\n\nGates: 2.G confidence · 3.G strategy · 4.3 printability · 8.1 output."
+ position: { x: 2360, y: 400 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-design"
+ name: "anno-design"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "green"
+ width: 520
+ height: 230
+ text: "## 2 · Make a new 3D-printable CAD file\n\n**3A Sourcing** first hunts an off-the-shelf / community substitute that drops in. If none matches, **3B Generative CAD** synthesises a continuation insert through the **B1–B8** trail (parameters → feature graph → B-rep → constraints → validation → DFM → functional check → STEP/STL export).\n\nGate **3.G** sends any *generated, load-bearing* part to a human before release."
+ position: { x: 3240, y: 420 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-resume"
+ name: "anno-resume"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "green"
+ width: 470
+ height: 220
+ text: "## 3 · Prove it, then resume production\n\n**Phase 4** re-parameterizes the design to locally-loaded stock; **4.3** blocks if the printability margin can't meet the duty load.\n\n**Phases 5–7** spin up a dedicated, ephemeral Render `cad-validator-{jobId}` service, emit the **v1 CAD output** (OpenSCAD + BOM + runbook), and run a canary→bulk validation. **8.1** is the fan-in acceptance gate.\n\nOn acceptance, **Finalize** seals the immutable audit trail and the **line resumes** on the v1 file until the OEM part arrives."
+ position: { x: 6760, y: 440 }
+ component: "annotation"
+ isCollapsed: false
+
+ edges:
+ # ingest → perception sensors (parallel fan-out)
+ - { sourceId: "ingest-trigger", targetId: "perc-conditioning", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-classification", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-dimensioning", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-material", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-telemetry", channel: "default" }
+ # 2B → 2C (reconstruction needs the identified part)
+ - { sourceId: "perc-classification", targetId: "perc-reconstruction", channel: "success" }
+ # sensors → merge (barrier)
+ - { sourceId: "perc-conditioning", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-reconstruction", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-dimensioning", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-material", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-telemetry", targetId: "perc-merge", channel: "success" }
+ # fuse → assemble → gate 2.G
+ - { sourceId: "perc-merge", targetId: "perc-assemble", channel: "success" }
+ - { sourceId: "perc-assemble", targetId: "gate-2g", channel: "success" }
+ - { sourceId: "gate-2g", targetId: "if-2g", channel: "success" }
+ - { sourceId: "if-2g", targetId: "design-sourcing", channel: "true" }
+ - { sourceId: "if-2g", targetId: "approve-2g", channel: "false" }
+ - { sourceId: "approve-2g", targetId: "design-sourcing", channel: "approved" }
+ - { sourceId: "approve-2g", targetId: "halt-aborted", channel: "rejected" }
+ # design: 3A sourcing → (matched? 3.G : 3B generative → 3.G)
+ - { sourceId: "design-sourcing", targetId: "if-sourced", channel: "success" }
+ - { sourceId: "if-sourced", targetId: "gate-3g", channel: "true" }
+ - { sourceId: "if-sourced", targetId: "design-generative", channel: "false" }
+ - { sourceId: "design-generative", targetId: "gate-3g", channel: "success" }
+ # gate 3.G
+ - { sourceId: "gate-3g", targetId: "if-3g", channel: "success" }
+ - { sourceId: "if-3g", targetId: "phase4-material", channel: "true" }
+ - { sourceId: "if-3g", targetId: "approve-3g", channel: "false" }
+ - { sourceId: "approve-3g", targetId: "phase4-material", channel: "approved" }
+ - { sourceId: "approve-3g", targetId: "halt-aborted", channel: "rejected" }
+ # printability + gate 4.3
+ - { sourceId: "phase4-material", targetId: "gate-43", channel: "success" }
+ - { sourceId: "gate-43", targetId: "if-43", channel: "success" }
+ - { sourceId: "if-43", targetId: "phase5-fabricate", channel: "true" }
+ - { sourceId: "if-43", targetId: "approve-43", channel: "false" }
+ - { sourceId: "approve-43", targetId: "phase5-fabricate", channel: "approved" }
+ - { sourceId: "approve-43", targetId: "halt-aborted", channel: "rejected" }
+ # provision + emit + validate + gate 8.1
+ - { sourceId: "phase5-fabricate", targetId: "gate-81", channel: "success" }
+ - { sourceId: "gate-81", targetId: "if-81", channel: "success" }
+ - { sourceId: "if-81", targetId: "finalize", channel: "true" }
+ - { sourceId: "if-81", targetId: "approve-81", channel: "false" }
+ - { sourceId: "approve-81", targetId: "finalize", channel: "approved" }
+ - { sourceId: "approve-81", targetId: "halt-aborted", channel: "rejected" }
+ # resume production
+ - { sourceId: "finalize", targetId: "notify-resumed", channel: "success" }
diff --git a/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.local.canvas.yaml b/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.local.canvas.yaml
new file mode 100644
index 0000000000..3e97c1d952
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/infra/superplane/kineticos-fleet.local.canvas.yaml
@@ -0,0 +1,504 @@
+# ─────────────────────────────────────────────────────────────────────────
+# KineticOS — the agent fleet, as a SuperPlane Canvas.
+#
+# THE EXACT PROCESS, end to end:
+# a CAD image of an industrial machine with a broken component arrives →
+# a fleet of perception agents LOOKS at it and IDENTIFIES what is wrong →
+# a design fleet RECONSTRUCTS the intended geometry and GENERATES a new
+# 3D-printable CAD file → printability + validation agents prove it will
+# hold → and on acceptance the line RESUMES PRODUCTION on that v1 file
+# until the OEM replacement arrives.
+#
+# SuperPlane is the BRAIN: it decides *when and whether* each agent runs, and
+# it parks the job at a human gate whenever confidence, the design trail, the
+# printability margin, or the output validation says so. Every TYPE_ACTION node
+# below is an `http` executor that calls one KineticOS agent endpoint; the agent
+# itself (Claude-backed, with a deterministic fallback) lives in the Next.js app.
+#
+# Replace the host `http://host.docker.internal:3000` with your KineticOS base URL
+# (e.g. http://localhost:3000 for local dev) — infra/superplane/apply.sh does
+# this substitution for you. The ingest trigger binds to the SuperPlane webhook
+# event source that KineticOS fires as SP_INGEST_WEBHOOK on POST /api/jobs.
+# ─────────────────────────────────────────────────────────────────────────
+apiVersion: v1
+kind: Canvas
+metadata:
+ name: "KineticOS — broken-machine → 3D-printable continuation fleet"
+ description: "Looks at a CAD image of a broken industrial machine, identifies the fault, generates a new 3D-printable CAD file, and resumes production — gated by SuperPlane at every phase."
+ isTemplate: false
+spec:
+ nodes:
+ # ───────────────── ingest (event source) ─────────────────
+ - id: "ingest-trigger"
+ name: "Ingest: broken-machine CAD image"
+ type: "TYPE_TRIGGER"
+ configuration: {}
+ position: { x: 120, y: 760 }
+ component: "webhook"
+ isCollapsed: false
+
+ # ───────────────── PERCEPTION FLEET — "look + identify what's wrong" ─────────────────
+ - id: "perc-conditioning"
+ name: "2A · Conditioning (image admissibility)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/conditioning"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 160 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-classification"
+ name: "2B · Classification (locate the break)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/classification"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 420 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-reconstruction"
+ name: "2C · Reconstruction (undamaged intent)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/reconstruction"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 1040, y: 420 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-dimensioning"
+ name: "2D · Dimensioning (interfaces + scale)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/dimensioning"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 920 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-material"
+ name: "2E · Material inference"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/material-infer"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 1160 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-telemetry"
+ name: "2F · Telemetry fusion"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/telemetry"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 600, y: 1400 }
+ component: "http"
+ isCollapsed: false
+ - id: "perc-merge"
+ name: "Fuse the perception sensors"
+ type: "TYPE_ACTION"
+ configuration:
+ enableStopIf: false
+ enableTimeout: true
+ executionTimeout: { unit: "minutes", value: 2 }
+ position: { x: 1480, y: 780 }
+ component: "merge"
+ isCollapsed: false
+ - id: "perc-assemble"
+ name: "Assemble PerceptionResult"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/perception-assemble"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 1920, y: 780 }
+ component: "http"
+ isCollapsed: false
+
+ # ───────────────── GATE 2.G · composite confidence ─────────────────
+ - id: "gate-2g"
+ name: "Gate 2.G · composite confidence"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "composite_confidence"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 2360, y: 780 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-2g"
+ name: "2.G clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 2800, y: 780 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-2g"
+ name: "Operator review · confirm the broken component"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 2800, y: 1080 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── DESIGN FLEET — "make a new 3D-printable CAD file" ─────────────────
+ - id: "design-sourcing"
+ name: "3A · Sourcing (off-the-shelf substitute)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/sourcing"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 3240, y: 780 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-sourced"
+ name: "Substitute found?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: "previous().data.body.matched == true"
+ position: { x: 3680, y: 780 }
+ component: "if"
+ isCollapsed: false
+ - id: "design-generative"
+ name: "3B · Generative CAD (B1–B8)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/generative-cad"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 45
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 3680, y: 1060 }
+ component: "http"
+ isCollapsed: false
+
+ # ───────────────── GATE 3.G · continuation strategy ─────────────────
+ - id: "gate-3g"
+ name: "Gate 3.G · continuation strategy"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "continuation_strategy"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 4120, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-3g"
+ name: "3.G clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 4560, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-3g"
+ name: "Operator review · the B1–B8 design trail"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 4560, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── PRINTABILITY + GATE 4.3 ─────────────────
+ - id: "phase4-material"
+ name: "4 · Printability adaptation (local stock)"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/stages/material"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 5, intervalSeconds: 5 }
+ position: { x: 5000, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "gate-43"
+ name: "Gate 4.3 · printability feasibility"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "printability"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 5440, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-43"
+ name: "4.3 clears?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 5880, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-43"
+ name: "Operator review · relax constraints or abort"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 5880, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── PROVISION + EMIT + VALIDATE (Render) + GATE 8.1 ─────────────────
+ - id: "phase5-fabricate"
+ name: "5–7 · Provision validator + emit CAD + validate"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/stages/fabricate-report"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 120
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "exponential", maxAttempts: 4, intervalSeconds: 10 }
+ position: { x: 6320, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "gate-81"
+ name: "Gate 8.1 · output acceptance"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/evaluate-gate"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ gate: "output_acceptance"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 6760, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "if-81"
+ name: "8.1 accepted?"
+ type: "TYPE_ACTION"
+ configuration:
+ expression: 'previous().data.body.decision == "proceed"'
+ position: { x: 7200, y: 880 }
+ component: "if"
+ isCollapsed: false
+ - id: "approve-81"
+ name: "Operator review · the v1 continuation output"
+ type: "TYPE_ACTION"
+ configuration:
+ items:
+ - type: "anyone"
+ position: { x: 7200, y: 1180 }
+ component: "approval"
+ isCollapsed: false
+
+ # ───────────────── RESUME PRODUCTION ─────────────────
+ - id: "finalize"
+ name: "8.3 · Seal run + resume production"
+ type: "TYPE_ACTION"
+ configuration:
+ method: "POST"
+ url: "http://host.docker.internal:3000/api/agents/finalize"
+ contentType: "application/json"
+ json:
+ job_id: "{{ root().data.job_id }}"
+ timeoutSeconds: 30
+ successCodes: "2xx"
+ retry: { enabled: true, strategy: "fixed", maxAttempts: 3, intervalSeconds: 5 }
+ position: { x: 7640, y: 880 }
+ component: "http"
+ isCollapsed: false
+ - id: "notify-resumed"
+ name: "✅ Line resumed on v1 continuation"
+ type: "TYPE_ACTION"
+ configuration: {}
+ position: { x: 8080, y: 880 }
+ component: "noop"
+ isCollapsed: false
+ - id: "halt-aborted"
+ name: "⛔ Halted — operator aborted"
+ type: "TYPE_ACTION"
+ configuration: {}
+ position: { x: 5000, y: 1560 }
+ component: "noop"
+ isCollapsed: false
+
+ # ───────────────── annotations (documentation widgets) ─────────────────
+ - id: "anno-overview"
+ name: "overview"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "blue"
+ width: 470
+ height: 360
+ text: "# KineticOS agent fleet\n\n**A CAD image of a broken industrial machine comes in → a modified, 3D-printable CAD file goes out, so the line keeps running.**\n\nSuperPlane is the brain: it decides *when and whether* each agent runs and parks the job at a human gate when confidence is low. Render runs the compute (the per-job `cad-validator-{jobId}` service).\n\nEvery action node is an `http` executor calling one KineticOS agent at `/api/agents/*`. Runs end-to-end at **zero credentials** — each agent has a deterministic fallback."
+ position: { x: 110, y: 200 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-perception"
+ name: "anno-perception"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "yellow"
+ width: 520
+ height: 250
+ text: "## 1 · Look & identify what is wrong\n\nSix perception agents fan out **in parallel** from the ingest event:\n\n- **2A Conditioning** — is the image even usable?\n- **2B Classification** — *which* component is broken (drives everything downstream).\n- **2C Reconstruction** — rebuild the *intended, undamaged* shape (never the cracked artifact). Needs 2B.\n- **2D Dimensioning** — the mating interfaces + a terminating scale chain.\n- **2E Material** · **2F Telemetry** — material/finish + sensor-fusion cross-check.\n\nThe **Merge** node waits for all sensors, then **Assemble** fuses them into one composite-confidence score."
+ position: { x: 600, y: -220 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-gates"
+ name: "anno-gates"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "red"
+ width: 470
+ height: 230
+ text: "## The gate pattern (×4)\n\nEvery phase ends in the same shape:\n\n`http evaluate-gate` → **If `decision == \"proceed\"`** → next phase.\n\nOtherwise the job parks at an **Approval** node — the human gate. SuperPlane scopes it to the *exact* weak field (the class, the overlay, one caliper reading). **Approved** resumes the flow; **Rejected** routes to **Halt**.\n\nGates: 2.G confidence · 3.G strategy · 4.3 printability · 8.1 output."
+ position: { x: 2360, y: 400 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-design"
+ name: "anno-design"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "green"
+ width: 520
+ height: 230
+ text: "## 2 · Make a new 3D-printable CAD file\n\n**3A Sourcing** first hunts an off-the-shelf / community substitute that drops in. If none matches, **3B Generative CAD** synthesises a continuation insert through the **B1–B8** trail (parameters → feature graph → B-rep → constraints → validation → DFM → functional check → STEP/STL export).\n\nGate **3.G** sends any *generated, load-bearing* part to a human before release."
+ position: { x: 3240, y: 420 }
+ component: "annotation"
+ isCollapsed: false
+ - id: "anno-resume"
+ name: "anno-resume"
+ type: "TYPE_WIDGET"
+ configuration:
+ color: "green"
+ width: 470
+ height: 220
+ text: "## 3 · Prove it, then resume production\n\n**Phase 4** re-parameterizes the design to locally-loaded stock; **4.3** blocks if the printability margin can't meet the duty load.\n\n**Phases 5–7** spin up a dedicated, ephemeral Render `cad-validator-{jobId}` service, emit the **v1 CAD output** (OpenSCAD + BOM + runbook), and run a canary→bulk validation. **8.1** is the fan-in acceptance gate.\n\nOn acceptance, **Finalize** seals the immutable audit trail and the **line resumes** on the v1 file until the OEM part arrives."
+ position: { x: 6760, y: 440 }
+ component: "annotation"
+ isCollapsed: false
+
+ edges:
+ # ingest → perception sensors (parallel fan-out)
+ - { sourceId: "ingest-trigger", targetId: "perc-conditioning", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-classification", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-dimensioning", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-material", channel: "default" }
+ - { sourceId: "ingest-trigger", targetId: "perc-telemetry", channel: "default" }
+ # 2B → 2C (reconstruction needs the identified part)
+ - { sourceId: "perc-classification", targetId: "perc-reconstruction", channel: "success" }
+ # sensors → merge (barrier)
+ - { sourceId: "perc-conditioning", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-reconstruction", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-dimensioning", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-material", targetId: "perc-merge", channel: "success" }
+ - { sourceId: "perc-telemetry", targetId: "perc-merge", channel: "success" }
+ # fuse → assemble → gate 2.G
+ - { sourceId: "perc-merge", targetId: "perc-assemble", channel: "success" }
+ - { sourceId: "perc-assemble", targetId: "gate-2g", channel: "success" }
+ - { sourceId: "gate-2g", targetId: "if-2g", channel: "success" }
+ - { sourceId: "if-2g", targetId: "design-sourcing", channel: "true" }
+ - { sourceId: "if-2g", targetId: "approve-2g", channel: "false" }
+ - { sourceId: "approve-2g", targetId: "design-sourcing", channel: "approved" }
+ - { sourceId: "approve-2g", targetId: "halt-aborted", channel: "rejected" }
+ # design: 3A sourcing → (matched? 3.G : 3B generative → 3.G)
+ - { sourceId: "design-sourcing", targetId: "if-sourced", channel: "success" }
+ - { sourceId: "if-sourced", targetId: "gate-3g", channel: "true" }
+ - { sourceId: "if-sourced", targetId: "design-generative", channel: "false" }
+ - { sourceId: "design-generative", targetId: "gate-3g", channel: "success" }
+ # gate 3.G
+ - { sourceId: "gate-3g", targetId: "if-3g", channel: "success" }
+ - { sourceId: "if-3g", targetId: "phase4-material", channel: "true" }
+ - { sourceId: "if-3g", targetId: "approve-3g", channel: "false" }
+ - { sourceId: "approve-3g", targetId: "phase4-material", channel: "approved" }
+ - { sourceId: "approve-3g", targetId: "halt-aborted", channel: "rejected" }
+ # printability + gate 4.3
+ - { sourceId: "phase4-material", targetId: "gate-43", channel: "success" }
+ - { sourceId: "gate-43", targetId: "if-43", channel: "success" }
+ - { sourceId: "if-43", targetId: "phase5-fabricate", channel: "true" }
+ - { sourceId: "if-43", targetId: "approve-43", channel: "false" }
+ - { sourceId: "approve-43", targetId: "phase5-fabricate", channel: "approved" }
+ - { sourceId: "approve-43", targetId: "halt-aborted", channel: "rejected" }
+ # provision + emit + validate + gate 8.1
+ - { sourceId: "phase5-fabricate", targetId: "gate-81", channel: "success" }
+ - { sourceId: "gate-81", targetId: "if-81", channel: "success" }
+ - { sourceId: "if-81", targetId: "finalize", channel: "true" }
+ - { sourceId: "if-81", targetId: "approve-81", channel: "false" }
+ - { sourceId: "approve-81", targetId: "finalize", channel: "approved" }
+ - { sourceId: "approve-81", targetId: "halt-aborted", channel: "rejected" }
+ # resume production
+ - { sourceId: "finalize", targetId: "notify-resumed", channel: "success" }
diff --git a/examples/kineticos-continuation-fleet/infra/superplane/smoke.sh b/examples/kineticos-continuation-fleet/infra/superplane/smoke.sh
new file mode 100755
index 0000000000..f183bf9fba
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/infra/superplane/smoke.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# Drive the KineticOS agent fleet end-to-end over HTTP, in the EXACT order the
+# SuperPlane canvas does — a faithful offline rehearsal of a canvas run. Runs at
+# zero credentials (every agent has a deterministic fallback).
+#
+# infra/superplane/smoke.sh [APP_BASE_URL] (default http://localhost:3000)
+#
+# Start the app first: npm run dev
+set -euo pipefail
+APP="${1:-http://localhost:3000}"
+
+post() { # post
+ curl -fsS -X POST "$APP$1" -H 'content-type: application/json' -d "$2"
+}
+field() { python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get(sys.argv[1],""))' "$1"; }
+show() { python3 -m json.tool; }
+
+echo "▸ ingest a broken-gearbox CAD image"
+JOB=$(post /api/jobs '{"cadImageUris":["s3://demo/gearbox-stage2-cad.png"],"telemetryUri":"s3://demo/gearbox-vibration.csv","assemblyContext":"gearbox stage 2 — driven idler","failureNote":"sheared tooth on idler gear"}' | field jobId)
+[ -n "$JOB" ] || { echo "✗ no jobId — is the app running at $APP?"; exit 1; }
+echo " job = $JOB"
+B="{\"job_id\":\"$JOB\"}"
+
+echo "▸ perception fleet (2A–2F fan out, then fuse)"
+for a in conditioning classification dimensioning material-infer telemetry; do
+ printf ' %-16s ' "$a"; post "/api/agents/$a" "$B" | python3 -c 'import sys,json;print(json.load(sys.stdin))'
+done
+printf ' %-16s ' reconstruction; post /api/agents/reconstruction "$B" | python3 -c 'import sys,json;print(json.load(sys.stdin))'
+echo "▸ assemble PerceptionResult"; post /api/agents/perception-assemble "$B" | show
+
+echo "▸ Gate 2.G · composite confidence"
+post /api/evaluate-gate "{\"job_id\":\"$JOB\",\"gate\":\"composite_confidence\"}" | show
+
+echo "▸ design fleet (3A sourcing → else 3B generative)"
+MATCHED=$(post /api/agents/sourcing "$B" | field matched)
+echo " sourcing matched = $MATCHED"
+if [ "$MATCHED" != "True" ] && [ "$MATCHED" != "true" ]; then
+ echo " → 3B generative continuation CAD (B1–B8)"
+ post /api/agents/generative-cad "$B" | show
+fi
+
+echo "▸ Gate 3.G · continuation strategy"
+post /api/evaluate-gate "{\"job_id\":\"$JOB\",\"gate\":\"continuation_strategy\"}" | show
+
+echo "▸ Phase 4 · printability adaptation"; post /api/stages/material "$B" | show
+echo "▸ Gate 4.3 · printability"; post /api/evaluate-gate "{\"job_id\":\"$JOB\",\"gate\":\"printability\"}" | show
+
+echo "▸ Phases 5–7 · provision validator + emit CAD + validate"; post /api/stages/fabricate-report "$B" | show
+echo "▸ Gate 8.1 · output acceptance"; post /api/evaluate-gate "{\"job_id\":\"$JOB\",\"gate\":\"output_acceptance\"}" | show
+
+echo "▸ 8.3 · seal run + resume production"; post /api/agents/finalize "$B" | show
+echo "✅ fleet complete — production resumed on the v1 continuation (job $JOB)"
diff --git a/examples/kineticos-continuation-fleet/src/app/api/agents/[agent]/route.ts b/examples/kineticos-continuation-fleet/src/app/api/agents/[agent]/route.ts
new file mode 100644
index 0000000000..c4f861171b
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/src/app/api/agents/[agent]/route.ts
@@ -0,0 +1,328 @@
+// ─────────────────────────────────────────────────────────────────────────
+// THE AGENT FLEET — one agent per call. POST /api/agents/{agent}
+//
+// The coarse /api/stages/* endpoints run a whole phase per call. These expose
+// ONE agent each, so a SuperPlane Canvas can drive the *exact process* at full
+// granularity: fan the six perception sensors out in parallel, fuse them, branch
+// the two design tracks, and gate between every phase. Each handler:
+// 1. loads the Job from the store (shared with the worker + coarse endpoints),
+// 2. runs exactly ONE agent module from src/agents/** (no domain logic here),
+// 3. persists its slice — perception sensors stash into an in-process scratch
+// keyed by jobId; `perception-assemble` composes the PerceptionResult from
+// it exactly as the worker does and writes job.perception,
+// 4. returns small flat JSON the Canvas routes on (see src/lib/contract.ts).
+//
+// Runs at zero credentials: every agent has a deterministic fallback, so the
+// whole fleet executes end-to-end offline. See infra/superplane/ for the Canvas
+// that wires these into nodes, gates, and the resume-production terminal.
+// ─────────────────────────────────────────────────────────────────────────
+
+import { NextResponse } from "next/server";
+import type {
+ Dimensions,
+ IdentifiedPart,
+ ImageAdmissibility,
+ Job,
+ MaterialClass,
+ PerceptionResult,
+ ReconstructedGeometry,
+ ScaleSource,
+} from "@/lib/types";
+import type {
+ AgentName,
+ ClassificationAgentResponse,
+ ConditioningAgentResponse,
+ DimensioningAgentResponse,
+ FinalizeAgentResponse,
+ GenerativeCadAgentResponse,
+ MaterialInferAgentResponse,
+ PerceptionAssembleResponse,
+ ReconstructionAgentResponse,
+ SourcingAgentResponse,
+ TelemetryAgentResponse,
+} from "@/lib/contract";
+import { appendAudit, upsertJob } from "@/lib/store";
+import { createAuditEntry } from "@/lib/audit";
+import { runConditioning } from "@/agents/perception/conditioning";
+import { runClassification } from "@/agents/perception/classification";
+import { runReconstruction } from "@/agents/perception/reconstruction";
+import { runDimensioning } from "@/agents/perception/dimensioning";
+import { runMaterialInference } from "@/agents/perception/material";
+import { runTelemetry } from "@/agents/perception/telemetry";
+import { runSourcing } from "@/agents/design/sourcing";
+import { runGenerativeCad } from "@/agents/design/generative-cad";
+import { compositeConfidence, errResponse, isLoadBearing, resolveJob } from "../../stages/_lib";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
+
+// ───────────────────────── perception scratch (in-process) ─────────────────────────
+// The six perception sensors run as independent nodes, so their outputs are
+// stashed here keyed by jobId until `perception-assemble` composes them into the
+// canonical PerceptionResult. Pinned to globalThis so every Next.js route bundle
+// shares one map (the store/worker/superplane pattern). Cleared on assemble.
+interface PerceptionScratch {
+ admissibility?: ImageAdmissibility[];
+ identifiedPart?: IdentifiedPart;
+ reconstructedGeometry?: ReconstructedGeometry;
+ dimensions?: Dimensions;
+ scaleSource?: ScaleSource | null;
+ dimensionalConfidence?: number;
+ inferredMaterialClass?: MaterialClass;
+ surfaceFinish?: string;
+ failureMode?: string | null;
+ sensorFusionAgreement?: number | null;
+}
+const g = globalThis as unknown as {
+ __kineticosScratch?: Map;
+};
+const scratchStore: Map =
+ g.__kineticosScratch ?? (g.__kineticosScratch = new Map());
+
+function scratch(jobId: string): PerceptionScratch {
+ let s = scratchStore.get(jobId);
+ if (!s) {
+ s = {};
+ scratchStore.set(jobId, s);
+ }
+ return s;
+}
+
+// Ordered for the GET discovery view + the Canvas layout.
+const AGENT_ORDER: AgentName[] = [
+ "conditioning",
+ "classification",
+ "reconstruction",
+ "dimensioning",
+ "material-infer",
+ "telemetry",
+ "perception-assemble",
+ "sourcing",
+ "generative-cad",
+ "finalize",
+];
+
+// ───────────────────────── the dispatch ─────────────────────────
+type Handler = (job: Job) => Promise;
+
+const HANDLERS: Record = {
+ // 2A — image admissibility
+ async conditioning(job) {
+ const admissibility = (await runConditioning(job)).data;
+ scratch(job.jobId).admissibility = admissibility;
+ const body: ConditioningAgentResponse = {
+ job_id: job.jobId,
+ admissible: admissibility.filter((a) => a.admissible).length,
+ images: admissibility.length,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2B — broken-component localization (drives every later stage)
+ async classification(job) {
+ const identified = (await runClassification(job)).data;
+ scratch(job.jobId).identifiedPart = identified;
+ const body: ClassificationAgentResponse = {
+ job_id: job.jobId,
+ part_class: identified.partClass,
+ conf_class: identified.confClass,
+ ambiguous_class: identified.ambiguousClass,
+ additional_parts: identified.additionalParts?.length ?? 0,
+ load_bearing: isLoadBearing(identified.partClass),
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2C — reconstruct the undamaged intent (needs 2B's identified part)
+ async reconstruction(job) {
+ const identified = scratch(job.jobId).identifiedPart;
+ if (!identified) {
+ return errResponse(409, "classification (2B) must run before reconstruction (2C)");
+ }
+ const reconstructed = (await runReconstruction(job, identified)).data;
+ scratch(job.jobId).reconstructedGeometry = reconstructed;
+ const body: ReconstructionAgentResponse = {
+ job_id: job.jobId,
+ method: reconstructed.method,
+ reconstruction_confidence: reconstructed.reconstructionConfidence,
+ failure_class: reconstructed.failureClass,
+ has_geometry: reconstructed.uri !== null,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2D — mating interfaces + terminal scale chain
+ async dimensioning(job) {
+ const dim = (await runDimensioning(job)).data;
+ const s = scratch(job.jobId);
+ s.dimensions = dim.dimensions;
+ s.scaleSource = dim.scaleSource;
+ s.dimensionalConfidence = dim.dimensionalConfidence;
+ const body: DimensioningAgentResponse = {
+ job_id: job.jobId,
+ scale_source: dim.scaleSource,
+ dimensional_confidence: dim.dimensionalConfidence,
+ parameters: Object.keys(dim.dimensions).length,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2E — material + surface inference
+ async "material-infer"(job) {
+ const material = (await runMaterialInference(job)).data;
+ const s = scratch(job.jobId);
+ s.inferredMaterialClass = material.inferredMaterialClass;
+ s.surfaceFinish = material.surfaceFinish;
+ const body: MaterialInferAgentResponse = {
+ job_id: job.jobId,
+ inferred_material_class: material.inferredMaterialClass,
+ surface_finish: material.surfaceFinish,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2F — telemetry track + sensor fusion
+ async telemetry(job) {
+ const tele = (await runTelemetry(job)).data;
+ const s = scratch(job.jobId);
+ s.failureMode = tele.failureMode;
+ s.sensorFusionAgreement = tele.sensorFusionAgreement;
+ const body: TelemetryAgentResponse = {
+ job_id: job.jobId,
+ failure_mode: tele.failureMode,
+ sensor_fusion_agreement: tele.sensorFusionAgreement,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 2.x — fuse the six sensors into the canonical PerceptionResult (worker parity)
+ async "perception-assemble"(job) {
+ const s = scratch(job.jobId);
+ if (!s.identifiedPart || !s.reconstructedGeometry || !s.dimensions || !s.admissibility) {
+ return errResponse(
+ 409,
+ "run conditioning, classification, reconstruction and dimensioning before perception-assemble",
+ );
+ }
+ const perception: PerceptionResult = {
+ admissibility: s.admissibility,
+ identifiedPart: s.identifiedPart,
+ reconstructedGeometry: s.reconstructedGeometry,
+ dimensions: s.dimensions,
+ scaleSource: s.scaleSource ?? null,
+ dimensionalConfidence: s.dimensionalConfidence ?? 0,
+ inferredMaterialClass: s.inferredMaterialClass ?? "unknown",
+ surfaceFinish: s.surfaceFinish ?? "unknown",
+ failureMode: s.failureMode ?? null,
+ sensorFusionAgreement: s.sensorFusionAgreement ?? null,
+ confTotal: compositeConfidence(
+ s.identifiedPart.confClass,
+ s.dimensionalConfidence ?? 0,
+ s.reconstructedGeometry.reconstructionConfidence,
+ s.sensorFusionAgreement ?? null,
+ ),
+ };
+ job.perception = perception;
+ await upsertJob(job);
+ scratchStore.delete(job.jobId);
+ await safeAudit(job.jobId, "perception-fleet", "assemble", `composite confidence ${perception.confTotal.toFixed(2)}`);
+
+ const body: PerceptionAssembleResponse = {
+ job_id: job.jobId,
+ conf_total: perception.confTotal,
+ conf_class: perception.identifiedPart.confClass,
+ dimensional_confidence: perception.dimensionalConfidence,
+ reconstruction_confidence: perception.reconstructedGeometry.reconstructionConfidence,
+ sensor_fusion_agreement: perception.sensorFusionAgreement,
+ ambiguous_class: perception.identifiedPart.ambiguousClass,
+ scale_source: perception.scaleSource,
+ load_bearing: isLoadBearing(perception.identifiedPart.partClass),
+ };
+ return NextResponse.json(body);
+ },
+
+ // 3A — off-the-shelf substitute federation (null → fall through to 3B)
+ async sourcing(job) {
+ if (!job.perception) return errResponse(409, "perception must run before sourcing (3A)");
+ const blueprint = (await runSourcing(job)).data;
+ if (blueprint) {
+ job.blueprint = blueprint;
+ await upsertJob(job);
+ }
+ const body: SourcingAgentResponse = {
+ job_id: job.jobId,
+ matched: blueprint !== null,
+ source_type: blueprint?.sourceType ?? null,
+ match_score: blueprint?.matchScore ?? null,
+ strategy: blueprint?.strategy ?? null,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 3B — generative continuation CAD, B1..B8 (the new 3D-printable file)
+ async "generative-cad"(job) {
+ if (!job.perception) return errResponse(409, "perception must run before generative-cad (3B)");
+ const blueprint = (await runGenerativeCad(job)).data;
+ job.blueprint = blueprint;
+ await upsertJob(job);
+ const body: GenerativeCadAgentResponse = {
+ job_id: job.jobId,
+ source_type: "generated",
+ strategy: blueprint.strategy,
+ load_bearing: isLoadBearing(job.perception.identifiedPart.partClass),
+ design_trail: blueprint.designTrail,
+ };
+ return NextResponse.json(body);
+ },
+
+ // 8.3 — seal the run; the line is resumed on the accepted v1 continuation
+ async finalize(job) {
+ job.status = "complete";
+ job.pendingGate = null;
+ job.auditTrail.push(
+ createAuditEntry(
+ "fleet",
+ "complete",
+ "continuation accepted — audit sealed, production resumed on the v1 CAD output",
+ ),
+ );
+ await upsertJob(job);
+ const body: FinalizeAgentResponse = {
+ job_id: job.jobId,
+ status: job.status,
+ resumed: true,
+ };
+ return NextResponse.json(body);
+ },
+};
+
+async function safeAudit(jobId: string, actor: string, action: string, detail: string): Promise {
+ try {
+ await appendAudit(jobId, createAuditEntry(actor, action, detail));
+ } catch {
+ // never fail the fleet on an audit write
+ }
+}
+
+// ───────────────────────── route handlers ─────────────────────────
+export async function POST(
+ req: Request,
+ { params }: { params: Promise<{ agent: string }> },
+) {
+ const { agent } = await params;
+ const handler = HANDLERS[agent as AgentName];
+ if (!handler) {
+ return errResponse(404, `unknown agent "${agent}" — one of: ${AGENT_ORDER.join(", ")}`);
+ }
+
+ let body: { job_id?: unknown };
+ try {
+ body = (await req.json()) as { job_id?: unknown };
+ } catch {
+ return errResponse(400, "invalid JSON body");
+ }
+ const resolved = await resolveJob(body.job_id);
+ if ("error" in resolved) return resolved.error;
+
+ return handler(resolved.job);
+}
diff --git a/examples/kineticos-continuation-fleet/src/app/api/agents/route.ts b/examples/kineticos-continuation-fleet/src/app/api/agents/route.ts
new file mode 100644
index 0000000000..300364553f
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/src/app/api/agents/route.ts
@@ -0,0 +1,26 @@
+// GET /api/agents — the fleet roster: every agent the Canvas can call, in
+// execution order, with the phase it implements. Discovery endpoint for the
+// SuperPlane Canvas builder + the docs in infra/superplane/.
+
+import { NextResponse } from "next/server";
+import type { AgentName } from "@/lib/contract";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
+
+const FLEET: { agent: AgentName; phase: string; label: string }[] = [
+ { agent: "conditioning", phase: "2A", label: "CAD image admissibility" },
+ { agent: "classification", phase: "2B", label: "broken-component localization" },
+ { agent: "reconstruction", phase: "2C", label: "reconstruct the undamaged intent" },
+ { agent: "dimensioning", phase: "2D", label: "mating interfaces + scale chain" },
+ { agent: "material-infer", phase: "2E", label: "material + surface inference" },
+ { agent: "telemetry", phase: "2F", label: "telemetry track + sensor fusion" },
+ { agent: "perception-assemble", phase: "2.x", label: "fuse sensors → PerceptionResult" },
+ { agent: "sourcing", phase: "3A", label: "off-the-shelf substitute federation" },
+ { agent: "generative-cad", phase: "3B", label: "generative continuation CAD (B1–B8)" },
+ { agent: "finalize", phase: "8.3", label: "seal the run, resume production" },
+];
+
+export async function GET() {
+ return NextResponse.json({ fleet: FLEET, count: FLEET.length });
+}
diff --git a/examples/kineticos-continuation-fleet/src/lib/contract.ts b/examples/kineticos-continuation-fleet/src/lib/contract.ts
new file mode 100644
index 0000000000..221c8c3904
--- /dev/null
+++ b/examples/kineticos-continuation-fleet/src/lib/contract.ts
@@ -0,0 +1,176 @@
+// ─────────────────────────────────────────────────────────────────────────
+// THE SHARED CONTRACT (types only) — the A↔B seam.
+//
+// The pivot divides at one seam: the SuperPlane App (Workstream A) calls this
+// Next app (Workstream B) over HTTP, and this app fires the Canvas webhook.
+// This file is the single source of truth for the wire shapes both sides agree
+// on. Person A imports the request/response types to build Canvas nodes; the
+// stage endpoints under src/app/api/stages/** + src/app/api/evaluate-gate
+// implement them verbatim.
+//
+// Wire fields are snake_case (so Canvas `if` expressions stay simple and flat);
+// the internal Job slices in ./types are camelCase. Each endpoint maps one to
+// the other. This module re-uses the canonical enums from ./types so the wire
+// contract can never silently drift from the domain model.
+// ─────────────────────────────────────────────────────────────────────────
+
+import type {
+ BlueprintSourceType,
+ DesignDecision,
+ GateDecisionType,
+ GateName,
+ MachineTarget,
+ ScaleSource,
+} from "./types";
+
+// ───────────────────────── stage endpoints (B implements, A calls) ─────────────────────────
+// Every stage endpoint takes exactly this, loads the Job from the store, runs
+// the existing agent module(s), persists the Job slice, and returns flat JSON.
+export interface StageRequest {
+ job_id: string;
+}
+
+/** POST /api/stages/perception → the composite-confidence routing fields. */
+export interface PerceptionStageResponse {
+ job_id: string;
+ conf_total: number;
+ conf_class: number;
+ dimensional_confidence: number;
+ reconstruction_confidence: number;
+ sensor_fusion_agreement: number | null; // null when no telemetry present
+ ambiguous_class: boolean;
+ scale_source: ScaleSource | null;
+ load_bearing: boolean;
+}
+
+/** POST /api/stages/design → the continuation-strategy routing fields. */
+export interface DesignStageResponse {
+ job_id: string;
+ source_type: BlueprintSourceType; // "sourced" | "generated"
+ match_score: number | null; // Track A only; null for a generated continuation
+ cad_uri_step: string | null;
+ cad_uri_stl: string | null;
+ load_bearing: boolean;
+ // Additive (beyond the flat wire contract): the B1..B8 design trail. The
+ // Canvas routes only on the flat fields and ignores this; the UI/audit render
+ // it. null on a sourced (Track A) substitute, which has no synthesis trail.
+ design_trail: DesignDecision[] | null;
+}
+
+/** POST /api/stages/material → printability + structural-feasibility fields. */
+export interface MaterialStageResponse {
+ job_id: string;
+ machine_target: MachineTarget | null; // "fdm" | "sla" | "cnc"
+ toolpath_uri: string | null;
+ structural_ok: boolean;
+ margin_pct: number;
+}
+
+/** POST /api/stages/fabricate-report → output-validation (QA) fields. */
+export interface FabricateReportResponse {
+ job_id: string;
+ dimensional_pass: boolean | null;
+ structural_pass: boolean | null;
+ in_process_anomalies: number;
+}
+
+// ───────────────────────── gate evaluation (optional convenience) ─────────────────────────
+// A can route purely on the confidence numbers with Canvas `if` nodes, OR call
+// this to reuse src/lib/gates/index.ts policy verbatim.
+export interface EvaluateGateRequest {
+ job_id: string;
+ gate: GateName; // composite_confidence | continuation_strategy | printability | output_acceptance
+}
+
+export interface EvaluateGateResponse {
+ decision: GateDecisionType; // "proceed" | "human_review" | "block"
+ reason: string;
+ scoped_field: string | null; // the precise field a human must resolve, if any
+}
+
+// ───────────────────────── granular agent fleet (B implements, A calls) ─────────────────────────
+// The coarse stage endpoints above run a whole phase per call. The agent-fleet
+// endpoints under /api/agents/[agent] expose ONE agent per call, so a SuperPlane
+// Canvas can fan the perception sensors out in parallel, branch the two design
+// tracks, and render the fleet at full granularity. Each takes { job_id }, runs
+// exactly one agent, persists its slice (perception sensors persist to an
+// in-process scratch the `perception-assemble` agent then composes), and returns
+// a small flat result the Canvas routes on. See infra/superplane/.
+export type AgentName =
+ | "conditioning" // 2A image admissibility
+ | "classification" // 2B broken-component localization
+ | "reconstruction" // 2C undamaged-intent reconstruction (needs 2B)
+ | "dimensioning" // 2D mating interfaces + scale chain
+ | "material-infer" // 2E material + surface inference
+ | "telemetry" // 2F telemetry track + sensor fusion
+ | "perception-assemble" // 2.x compose PerceptionResult from the sensors
+ | "sourcing" // 3A off-the-shelf substitute federation
+ | "generative-cad" // 3B generative continuation CAD (B1–B8)
+ | "finalize"; // 8.3 seal the run, mark the line resumed
+
+export interface ConditioningAgentResponse {
+ job_id: string;
+ admissible: number; // count of admissible images
+ images: number; // total images assessed
+}
+export interface ClassificationAgentResponse {
+ job_id: string;
+ part_class: string;
+ conf_class: number;
+ ambiguous_class: boolean;
+ additional_parts: number; // other broken components in the same image
+ load_bearing: boolean;
+}
+export interface ReconstructionAgentResponse {
+ job_id: string;
+ method: string;
+ reconstruction_confidence: number;
+ failure_class: string | null;
+ has_geometry: boolean; // false → confidence below gate → human overlay
+}
+export interface DimensioningAgentResponse {
+ job_id: string;
+ scale_source: ScaleSource | null;
+ dimensional_confidence: number;
+ parameters: number; // count of resolved dimensional parameters
+}
+export interface MaterialInferAgentResponse {
+ job_id: string;
+ inferred_material_class: string;
+ surface_finish: string;
+}
+export interface TelemetryAgentResponse {
+ job_id: string;
+ failure_mode: string | null;
+ sensor_fusion_agreement: number | null;
+}
+/** perception-assemble returns the SAME routing shape as the coarse endpoint. */
+export type PerceptionAssembleResponse = PerceptionStageResponse;
+export interface SourcingAgentResponse {
+ job_id: string;
+ matched: boolean; // false → fall through to the generative track (3B)
+ source_type: BlueprintSourceType | null;
+ match_score: number | null;
+ strategy: string | null;
+}
+export interface GenerativeCadAgentResponse {
+ job_id: string;
+ source_type: "generated";
+ strategy: string;
+ load_bearing: boolean;
+ design_trail: DesignDecision[] | null; // B1..B8
+}
+export interface FinalizeAgentResponse {
+ job_id: string;
+ status: string; // "complete"
+ resumed: boolean; // production resumed on the v1 continuation
+}
+
+// ───────────────────────── intake → Canvas webhook (A defines, B fires) ─────────────────────────
+// POST /api/jobs creates the Job, then POSTs this to SP_INGEST_WEBHOOK with the
+// header `X-Webhook-Token: `. (Firing is Workstream B2.)
+export interface IngestWebhookPayload {
+ job_id: string;
+ image_uris: string[];
+ telemetry_uri: string | null;
+}