Developers

Autonomy policy is code. Review it like code.

The Pulpum SDK is typed Python. Tools declare their tags, units and limits; agents declare their goal, tool set and autonomy level; the policy engine enforces all of it at call time. Every run is replayable against a new model before that model goes near a machine.

Typed Python SDKCLI + RESTDeterministic replay

run_8f21c4 · agent graph · PM4 10 nodes · 1 approval gate
01 · orchestrator ingest.grade_target ✓ SUCCEEDED 02 · orchestrator twin.simulate ✓ SUCCEEDED 03 · pulp_stock stock.refine ✓ SUCCEEDED 04 · wetend_chem wetend.dose ✓ SUCCEEDED 05 · form_press form.headbox ✓ SUCCEEDED 06 · form_press press.nip ✓ SUCCEEDED 07 · dry_coat dry.steam ✓ SUCCEEDED 08 · defect_inspect inspect.web ✓ SUCCEEDED 09 · orchestrator approve.human ◆ APPROVAL 10 · quality_conf reel.qualify ✓ SUCCEEDED

Live example: Grade change PM4 · 135 gsm kraftliner → 110 gsm testliner, no break, ≤14 min off-spec

Developers

Define an agent, bound it, run it

The Pulpum SDK is typed Python. Tools are declared with schemas and limits; the policy engine enforces them at call time — not in a review meeting.

What you get

  • Typed tool definitions with unit-aware ranges and rate limits
  • Deterministic replay of any historical run against a new model
  • Local twin harness so a recipe is simulated before it is shipped
  • Autonomy policy as code, versioned and reviewed like any other change

Read the docs Developer guide

pm4_dryer_agent.py
# Bound the dryer agent to six steam groups on PM4.
from pulpum import Agent, Tool, Limit, Autonomy

steam = Tool(
    name="dcs.steam_schedule",
    tags=["PM4.DRY.G1..G6.PRESS_SP"],
    limits=[Limit(max_step="0.15 bar", per="30s")],
)

dryer = Agent(
    id="agent.dry_coat",
    goal="reel moisture 7.4% +/-0.5, min steam",
    tools=[steam, Tool("qcs.read_moisture", read_only=True)],
    # bounded writes; humans still gate ramps
    autonomy=Autonomy.L3,
    # simulate on the twin before every write
    verify="twin",
)

run = dryer.start(machine="PM4", grade="TL-110")
for step in run.stream():
    print(step.name, step.status, step.duration)
CLI

Start a run from anywhere

The same run engine, the same policy checks, the same audit trail — from the terminal, the HMI or the SDK.

pulpum · cli
$ pulpum run "grade change PM4 to TL-110" --autonomy L3

→ plan composed          10 steps · 1 approval gate
→ twin.simulate          48 candidates · best #31 · risk 0.07
→ policy.evaluate        9 writes permitted · 1 held for human
→ executing              stock.refine ... ok   2m10s
→ executing              wetend.dose .... ok   1m26s
→ executing              form.headbox ... ok   1m05s
→ executing              dry.steam ...... ok   3m18s
! approval required      speed_ramp 1180 → 1245 m/min
→ approved               J. Okonkwo · machine tender · 04:57:12
→ run complete           11m42s · off-spec 6.2 t · breaks 0

$ pulpum runs show run_8f21c4 --format genealogy
Capabilities

What the SDK gives you

Enough to extend the platform; not enough to bypass its guardrails.

Tools

Typed, unit-aware definitions

Declare tags, direction, engineering units, magnitude and rate limits. The policy engine validates every call against them.

Agents

Goal, tools, autonomy

An agent is a goal statement, a bounded tool set, a verification strategy and an autonomy level. Nothing more implicit than that.

Policy

Autonomy as code

Levels, approval chains and tag classes are versioned files, reviewed in a pull request like any other change.

Twin harness

Simulate locally

Run a candidate recipe against a local twin snapshot before it ever reaches a mill-edge node.

Replay

Deterministic re-runs

Replay any historical run against a new model version and diff the decisions step by step.

Streams

Subscribe to runs

Server-sent events for run progression, tool calls and status changes, with the same payloads the console shows.

API

The run API

Three resources: runs, steps and approvals. Everything else is a read on the audit log.

Selected endpoints from the Pulpum run API v1
MethodPathPurpose
POST/v1/runsStart a run from a goal and a machine
GET/v1/runs/{id}Run record: plan, steps, statuses, durations
GET/v1/runs/{id}/streamServer-sent events for live progression
POST/v1/runs/{id}/cancelStop a run and hand control back to the DCS
GET/v1/approvalsPending human approval gates
POST/v1/approvals/{id}Approve or reject, with the approver identity
POST/v1/twin/simulateRank candidate recipes without executing
GET/v1/reels/{id}/genealogyFull reel record for release and audit
GET/v1/auditAppend-only log, filterable and exportable
Run timeline

What the API returns

A run record is exactly what the console renders — no privileged view, no hidden fields.

run_8f21c4 PM4 · 6.8 m trim · 1,180 m/min SUCCEEDED
  1. 01 ingest.grade_target SUCCEEDED 0.8 s

    Mill Orchestrator — Pulled the 110 gsm testliner spec, customer tolerances and the standing energy budget from mill MES; locked the target envelope for the run.

  2. 02 twin.simulate SUCCEEDED 34 s

    Mill Orchestrator — Simulated 48 candidate transition recipes on the as-run paper-machine twin — forming, press, dryer and calender — and ranked them on off-spec tonnes, break risk and steam.

  3. 03 stock.refine SUCCEEDED 2 m 10 s

    Pulp-and-Stock — Stepped refiner specific edge load 1.9 → 1.4 Ws/m and pushed freeness toward 412 CSF while consistency held at 3.4%.

  4. 04 wetend.dose SUCCEEDED 1 m 26 s

    Wetend-and-Chemistry — Retention aid trimmed to 214 g/t and sizing to 1.1 kg/t against live charge and turbidity; first-pass retention recovered to 78% inside 90 seconds.

  5. 05 form.headbox SUCCEEDED 1 m 05 s

    Form-and-Press — Re-cut the slice profile across 78 actuators and set jet-to-wire to 0.994 to hold formation index through the basis-weight ramp.

  6. 06 press.nip SUCCEEDED 48 s

    Form-and-Press — Nip load reduced 620 → 540 kN/m to protect the lighter web; post-press dryness landed at 47.1%.

  7. 07 dry.steam SUCCEEDED 3 m 18 s

    Dry-and-Coat — Re-phased the steam schedule across 6 dryer groups and rebalanced the hood; reel moisture converged to 7.4% ±0.19 2σ at 6.1% less steam than the standing recipe.

  8. 08 inspect.web SUCCEEDED continuous

    Defect-and-Inspect — 18 line-scan cameras streaming; two edge-crack precursors detected at the drive side and cleared by a 40 kN/m nip trim before either propagated.

  9. 09 approve.human APPROVAL 52 s

    Mill Orchestrator — Speed ramp 1,180 → 1,245 m/min exceeded the site autonomy threshold. Held for the machine tender; approved by J. Okonkwo at 04:57:12.

  10. 10 reel.qualify SUCCEEDED 1 m 31 s

    Quality-and-Conformance — Reel R-24188 released: full genealogy written (furnish, chemistry, CD profiles, defect map, operator actions) and checked against grade spec.

Tool calls

Streaming a run

The SSE payloads are the same records shown here, one event per tool call and result.

tool-call stream · run_8f21c4
  1. 04:51:04mes.read_orders({ machine: "PM4", next_grade: "TL-110" })
  2. 04:51:04ok spec locked · basis 110 g/m² · moisture 7.4% ±0.5
  3. 04:51:05twin.simulate({ candidates: 48, horizon: "18 min" })
  4. 04:51:39ok best recipe #31 · est. off-spec 6.4 t · break risk 0.07
  5. 04:51:40policy.evaluate({ recipe: 31, autonomy: "L3" })
  6. 04:51:40ok 9 writes permitted · 1 write requires human approval
  7. 04:51:41dcs.write_setpoint({ tag: "REF4.SEL", from: 1.9, to: 1.4 })
  8. 04:53:51ok freeness 388 → 412 CSF · consistency 3.4%
  9. 04:53:52chem.dose({ retention_aid: "214 g/t", sizing: "1.1 kg/t" })
  10. 04:55:18ok first-pass retention 71% → 78%
  11. 04:55:19dcs.slice_profile({ actuators: 78, jet_to_wire: 0.994 })
  12. 04:56:24ok formation index 84 · CD basis 2σ 1.7 g/m²
  13. 04:56:25vision.stream({ cameras: 18, model: "webdefect-v7" })
  14. 04:56:41precursor: edge crack risk 0.62 · drive side · 1,412 mm
  15. 04:56:42dcs.nip_load({ delta: "-40 kN/m", zone: "press-2" })
  16. 04:56:58ok edge crack risk 0.62 → 0.09 · web stable
  17. 04:56:59approval.request({ action: "speed_ramp", to: "1245 m/min" })
  18. 04:57:12ok approved by J. Okonkwo (machine tender) · logged
  19. 04:58:44mes.log_reel({ reel: "R-24188", genealogy: "full" })
  20. 05:02:46ok run complete · 11m42s · off-spec 6.2 t · breaks 0

Reasoning trace

Plan → thought → action → observation, exactly as the orchestrator recorded it.

GoalMove PM4 from 135 gsm kraftliner to 110 gsm testliner with zer…

Move PM4 from 135 gsm kraftliner to 110 gsm testliner with zero breaks and under 14 minutes of off-spec production.

PlanSimulate the transition on the twin first. Lead with stock and…

Simulate the transition on the twin first. Lead with stock and chemistry so the furnish is right before the basis weight ramps, then trim forming and press, then re-phase drying. Watch the web continuously; hold the speed ramp for a human.

ThoughtThe standing recipe drops basis weight and speed together. On …

The standing recipe drops basis weight and speed together. On the twin that produces a 3-minute moisture excursion because the dryer section unloads faster than the steam schedule can follow. Sequencing the steam re-phase ahead of the basis-weight ramp removes it.

Actiontwin.simulate(candidates=48) → recipe #31 re-phases steam 40 s…

twin.simulate(candidates=48) → recipe #31 re-phases steam 40 s before the basis-weight ramp and holds nip load until post-press dryness confirms.

ObservationRecipe #31 estimates 6.4 t off-spec against 9.8 t for the stan…

Recipe #31 estimates 6.4 t off-spec against 9.8 t for the standing recipe, with break risk 0.07. Two candidates scored lower on steam but raised break risk above the 0.15 site limit and were discarded.

ActionExecute recipe #31 under autonomy level L3: nine setpoint writ…

Execute recipe #31 under autonomy level L3: nine setpoint writes permitted, speed ramp routed to the machine tender.

ObservationEdge-crack precursor at 1,412 mm drive side at 04:56:41. Nip t…

Edge-crack precursor at 1,412 mm drive side at 04:56:41. Nip trim of −40 kN/m in press-2 cleared it in 16 s without a break.

OutcomeRun complete in 11 m 42 s. 6.2 t off-spec, zero breaks, 6.1% l…

Run complete in 11 m 42 s. 6.2 t off-spec, zero breaks, 6.1% less dryer steam than the standing recipe. Reel R-24188 released with full genealogy.

Workflow

From idea to a machine, safely

The path a new agent or model takes before it can write to production.

  1. Define

    Write the tool definitions and the agent goal. Limits are mandatory; there is no unbounded write tool to fall back on.

  2. Simulate

    Run against a local twin snapshot and a library of historical scenarios, including rare break and upset cases.

  3. Replay

    Re-run 90 days of historical runs and diff every decision against the incumbent model. Unexplained divergences block promotion.

  1. Evaluate

    Golden datasets plus LLM-as-judge on the reasoning traces, gated in CI. A failing evaluation fails the build.

  2. Shadow

    Deploy to the mill edge in shadow mode. It records what it would have done and writes nothing.

  3. Promote

    Advisory, then supervised, then bounded — each step gated by the site, with one-command rollback by model version.

Autonomy

Four levels, set per agent and per tag

A mill does not go from manual to unattended in one step. Pulpum makes the level explicit, auditable and reversible at any time.

Autonomy levels and the human role at each
LevelWhat the agent doesWhat the human doesTypical time to reach
L1 · AdvisoryRecommends setpoints and explains whyEnters every change manuallyWeek 1
L2 · SupervisedProposes a write; it executes on approvalApproves each write in the HMIWeek 3–6
L3 · BoundedWrites inside tag, rate and magnitude limitsApproves ramps and grade releasesMonth 2–4
L4 · UnattendedRuns the envelope without promptingSets the envelope; reviews the shift recordMonth 6+ [ASPIRATIONAL]
Multi-agent handoff

Agents negotiate, they do not collide

Two agents will want the same actuator. The orchestrator arbitrates on the run goal, not on who asked first — and the handoff is logged like any other step.

Contested move: press-2 nip load

  1. 01form_press.request(nip −0 kN/m) SUCCEEDED0.2 s

    Form-and-Press wants to hold nip load to protect post-press dryness.

  2. 02defect_inspect.request(nip −40 kN/m) SUCCEEDED0.2 s

    Defect-and-Inspect wants to reduce nip load to clear an edge-crack precursor.

  3. 03orchestrator.arbitrate SUCCEEDED0.4 s

    Break risk 0.62 outranks a 0.4-point dryness loss under the run goal "zero breaks". Defect-and-Inspect wins the actuator for 120 s.

  4. 04form_press.handoff(returned) SUCCEEDED120 s

    Actuator returned; Form-and-Press recovers dryness with vacuum instead. Post-press dryness lands at 47.1%.

Arbitration rules

  • Run goal first. Every request is scored against the declared goal, not the requesting agent's local objective.
  • Safety and breaks outrank quality. A break costs hours; a profile excursion costs minutes.
  • Time-boxed ownership. An agent holds a contested actuator for a bounded window, then must re-justify.
  • Everything is logged. The losing request, the score and the reason all appear in the run record.
  • Deadlocks escalate to a human rather than resolving by timeout.

See the agent roster

Integrations

It speaks mill, not cloud

Pulpum reads and writes through the systems already on the floor. No rip-and-replace, no parallel historian, no new HMI to learn.

QCS

Valmet IQ, ABB 800xA QCS, Honeywell Experion MX

Profiles, scans, lab results

DCS

ABB 800xA, Valmet DNA, Honeywell Experion, Siemens PCS 7

Setpoint reads and guarded writes

Web inspection

WIS/WMS line-scan, IR and transmission cameras

Frames, defect maps, break replays

Mill MES

SAP PP/QM, ABB cpmPlus, custom historians

Orders, grades, reel genealogy

Historian

OSIsoft PI, Aspen IP.21, InfluxDB

Time-series backfill and replay

Robotics

NVIDIA Isaac, winder and wrapper PLCs

Reel, roll and clamp-truck motion

Identity

Azure AD, Okta, on-prem LDAP

SSO, RBAC, named approvers

Edge

NVIDIA Jetson Orin, IGX, on-prem GPU

Sub-100 ms inference at the machine

See all integrations

Guardrails

An agent that can move a nip needs a leash

Pulpum writes to production equipment. Every capability is scoped, every write is policy-checked, and every action is written to an append-only audit log the mill owns.

  • Bounded action space. Each agent can only write to an explicit tag allow-list, inside per-tag rate and magnitude limits.
  • Policy engine before every write. Autonomy level, shift, grade, interlock state and operator presence are all evaluated before a setpoint moves.
  • Human-in-the-loop gates. Anything above the site threshold — speed ramps, grade releases, safety-adjacent moves — waits for a named approver.
  • Immutable audit log. Append-only, hash-chained, exportable, and retained on the mill's own storage.
  • Hard fallback. Loss of the edge node, the network or the model returns control to the DCS's last known-good state within one scan cycle.
  • Tenant and IP isolation. Furnish recipes, grade models and defect libraries never cross a customer boundary. On-prem deployment available.

Compliance posture

Compliance and certification status
StandardScopeStatus
SOC 2 Type IICloud control plane RUNNING In progress [ASPIRATIONAL]
ISO 27001Company-wide ISMS QUEUED Planned [ASPIRATIONAL]
IEC 62443Mill-edge OT security RUNNING Design-aligned
GDPROperator data SUCCEEDED Compliant
ISO 9001 / FSCQuality + chain of custody records SUCCEEDED Supported

Read the security overview

FAQ

Straight answers

The questions mill managers and process engineers actually ask in the first meeting.

Python is the first-class SDK. Everything the SDK does is available over the REST API, so any language can start runs, read records and respond to approvals.

Get started

Build on the run engine

Get SDK access, the sandbox mill and the tool-definition reference.