llms-txt

Stateloom — The Suite That Runs a Chart

The published @miadi/stateloom-* family: packages and versions, SMDF and ERDF, the MCP tool surface, the smcx terminal, the agent skills, and the environment contract. The runtime named by llms-stc-state-machine.md §6.

Pulled, not written. This file is generated by __PULL_upstream.sh from jgwill/smcraft (llms.txt). jgwill/smcraft owns these facts; correct them there and the next pull carries the correction here. An edit made in this file is lost at that pull.

Canonical: https://docs.smcraft.jgwill.com/llms.txt

pull-content-sha256: 981cfc63af218889


Design, validate, generate and run hierarchical state machines. One declarative format (SMDF) read by an engine, a code generator, an MCP server, a terminal CLI, a socket.io hub and a browser canvas — so an agent, a person at a shell and a person at a canvas can all edit the same document while it is open.

Engine: @miadi/stateloom-engine — 0.4.4 (npm; renamed from smcraft, deprecated) / miadi-stateloom-engine 0.2.1 (PyPI, CLI smcg) Loom packages: @miadi/stateloom-* — protocol, client, hub, react, canvas, cli, mcp, skills, web Framework: RISE v1.2 (Reverse-engineer → Intent-extract → Specify → Export) Repository: https://github.com/jgwill/smcraft Published at: https://docs.smcraft.jgwill.com

Packages

Ten published npm packages, plus the Python engine on PyPI. The designer source (web/) stays "private": true; its prebuilt standalone build ships as @miadi/stateloom-web.

Install What it is
npm i @miadi/stateloom-engine (0.4.4) The engine. SMDF parser, validator (a subset of V001–V014 — see below), hierarchical runtime, the Machine SMDF interpreter, TypeScript code generator.
pip install miadi-stateloom-engine (0.2.1) The Python twin of the engine: the full validator V001–V014, the Python code generator, and the smcg CLI.
npm i @miadi/stateloom-protocol (0.1.8) Zero-runtime-dependency foundation: patch ops, diff/apply, wire envelopes, layout, edge routing, viewport math, ASCII/Mermaid render, export naming, env aliasing.
npm i @miadi/stateloom-client (0.1.2) Framework-agnostic socket.io-client wrapper: join / patch / full / presence with auto-resync.
npm i @miadi/stateloom (0.1.4) The socket.io hub. Bin smcraft-bridge.
npm i @miadi/stateloom-react (0.1.6) React 19 binding: useSmcraftBridge, session core. Re-exports the protocol’s viewport helpers at the address the web designer has always imported them from.
npm i @miadi/stateloom-canvas (0.1.4) The design surface as a mountable component: <StateMachineCanvas> — wheel/⌃wheel/middle-drag/Space+drag/pinch navigation, composite drill-down, box dragging, routed edges, settled event chips, zoom HUD. Props in, callbacks out; every colour a --slc-* CSS variable. Ship @miadi/stateloom-canvas/styles.css with it.
npm i -g @miadi/stateloom-cli (0.1.3) Bin smcx — drive the loom from a terminal.
npx -y @miadi/stateloom-mcp (0.2.6) The MCP server. Bins stateloom-mcp and legacy smcraft-mcp.
npx -y @miadi/stateloom-skills (0.3.2) Bin stateloom — installs agent skills into .claude/skills/.
npx -y @miadi/stateloom-web (0.1.9) Bin stateloom-web — the visual designer, prebuilt. Serves on 4598; reads its bridge URL at runtime from GET /api/config, so a published build carries nobody’s URL and a redeploy is a restart, never a rebuild.

Dependency direction: everything points at @miadi/stateloom-protocol. Nothing points back.

stateloom-protocol ──┬── stateloom-client ──┬── stateloom-react ──┬── web/ (designer)
                     │                      └── stateloom-mcp     │
                     ├── stateloom-canvas ───────────────────────┬─┘
                     │                                           └── forgewright
                     ├── stateloom (hub) ─── stateloom-cli ── stateloom-mcp
                     └── (render, layout, viewport, exportName, env used by all)
stateloom-engine ── generated code imports the runtime at execution time

The engine — @miadi/stateloom-engine

npm subpath exports:

Python mirrors this as stateloom.model, stateloom.parser, stateloom.runtime, stateloom.codegen, stateloom.cli. ObserverLogger (Python logging) exists on the Python side only.

Each engine generates its own language. The npm package exports TypeScriptCodeGenerator; the PyPI package exports PythonCodeGenerator and carries smcg, which accepts -l python and nothing else. “Python and TypeScript codegen” describes the pair of packages, never either one — and it is why the web designer’s TypeScript generate button, which shells out to smcg, does not work yet (Spec 72).

SMDF — State Machine Definition Format

Extension .smdf.json (also .smdf.xml, legacy .fsm). Three top-level sections:

State classification is derived from structure, not declared: leaf (no children), composite (states[]), final (kind: "final"), history (kind: "history"), parallel (parallel), root (top of tree). Validation rules V001–V014 cover uniqueness, reference integrity, final-state and parallel-region constraints, and timer references.

Which validator you are holding matters. The format specifies fourteen rules; the implementations do not all carry fourteen. The Python engine implements V001–V014. The TypeScript engine implements V001, V002, V003, V005, V006, V007, V013 — so V009 (composite with no non-final initial child), V011 (parallel region targets) and V012 (empty composite) are found by smcg and not by validate() in JS. The MCP server’s validate_definition runs its own reference/uniqueness set whose rule IDs are not the engine’s: its V001 is “no events defined”, its V004 an unknown transition target, its V005 “root has no child state”. Read the message, not the number.

Full schema: Spec 70 — SMDF Format

MCP server — 17 state-machine tools, 10 ERD tools

@miadi/stateloom-mcp, stdio by default (Streamable HTTP optional — see Transport below). Registration:

{
  "mcpServers": {
    "stateloom": {
      "command": "npx",
      "args": ["-y", "@miadi/stateloom-mcp"],
      "env": {
        "STATELOOM_PROJECT_FILE": "/absolute/path/to/machine.smdf.json",
        "STATELOOM_BRIDGE_URL": "http://127.0.0.1:4599"
      }
    }
  }
}

Build — create_state_machine(namespace, name, asynchronous?), add_state(name, parent?, kind?, description?), add_event(id, description?), add_transition(state, event, nextState?, condition?, description?), remove_state(name)

Read — get_definition(), load_definition(json), list_states(), list_events()

Act — validate_definition(), generate_code(language), generate_rispec(intent?), render_diagram(format?, path?, scale?, theme?, stamp?, open?)

Document — set_project_file(path), get_project_file()

Notes (either document type) — get_notes(), set_notes(target?, notes): working notes on the diagram or on one state / entity, saved in the document (settings.notes, notes on a shape) for whoever opens it next. Read them before changing a diagram someone else worked on. Engines and codegen ignore them

Plus resource smcraft://definition and prompt design-state-machine(domain?, name?).

Transport. Stdio by default — the server runs as a child process beside one agent. Setting STATELOOM_MCP_HTTP_PORT serves Streamable HTTP at POST /mcp instead (plus GET /health), for agents on other machines. HTTP mode requires STATELOOM_MCP_TOKEN and refuses to start without it, binds loopback unless told otherwise, and is not multi-tenant: the active document is process state, so every client shares one board. STATELOOM_MCP_ROOT bounds where that document can move and STATELOOM_MCP_LOCK_PROJECT pins it.

Three of these carry more than their names say:

Details: Spec 73 — MCP Server, Spec 76 — RISE Rispec Generator

ERDF — Entity-Relationship Definition Format

The sibling of SMDF: a .erdf.json describes the data a system’s machines act on — settings, entities[] (each with attributes[]: name, type, key pk uk, references, nullable, stateOf), relationships[] (from, to, cardinality 1:1 1:N N:1 N:M, label). One ERDF accompanies any number of SMDF documents; the link is by name only — an SMDF settings.objects[].class names an entity, a guard’s <instance>.<field> names an attribute, and an attribute’s stateOf names a machine.

The loom weaves one active document and its type is its extension. set_project_file takes a .smdf.json or a .erdf.json; get_definition, load_definition and render_diagram (mermaid only for an ERD) answer for whichever is active; the state-machine tools refuse to write over an ERD.

ERD tools — create_erd(namespace, name, description?, path?, overwrite?) (also makes it the active document), add_entity (weak? for an entity that exists only through another), update_entity, add_attribute, add_relationship, remove_entity, remove_attribute, remove_relationship, validate_erd() (rules E001–E005), check_links(erd_path?, smdf_paths?) (rules L001–L004 — what a machine names that the ERD does not carry).

From @miadi/stateloom-protocol: validateErd, checkLinks, checkStateOf, renderMermaidEr, erdAutoLayout, and the pure edits addEntity … removeRelationship. From @miadi/stateloom-canvas: <EntityRelationshipCanvas>. The web designer opens its ERD workspace for any ?doc=<path>.erdf.json.

Two drawings of one document. <EntityRelationshipCanvas notation="crowsfoot" | "chen"> and the ▤ / ◇ switch in the web designer choose between crow’s foot (attributes as rows in the entity box) and Chen (attribute ovals, relationship diamonds, underlined keys, double rectangle for a weak entity). The notation is the viewer’s choice and is never written to the file; tools and code generation read the same ERDF either way. erdAutoLayout(def, { notation }) lays out for either.

Examples: examples/library_lending.erdf.json, examples/trading_strategy.erdf.json. Full spec: Spec 80 — ERDF Format

Terminal — smcx

Global options: --bridge <url>, --doc <path>, --name <label>.

smcx serve [--port n] [--host h] [--file path]     boot the hub
smcx add-state <name> [--parent] [--kind] [--desc]
smcx add-event <id> [--source n] [--desc]
smcx add-transition <state> <event> [--to] [--when]
smcx remove-state <name>
smcx load <file>
smcx watch [--as ascii|mermaid]                    stream the live doc
smcx render [--as svg|png|mermaid|ascii] [--out path|-] [--scale n]
            [--theme dark|light] [--title text] [--stamp] [--open]
smcx open [--web url]
smcx presence

Mutations are durable-first: the definition is written to disk, then emitted to the hub. A machine can be edited with no hub running at all.

render reads the durable .smdf.json straight off disk — no hub, no browser, no agent. png goes through whichever rasterizer the host has: sharp (if installed) → librsvg → Inkscape → ImageMagick → headless Chrome, tried in that order. svg needs none of them.

Agent skills — stateloom

npx -y @miadi/stateloom-skills skills list
npx -y @miadi/stateloom-skills skills install <name>
npx -y @miadi/stateloom-skills skills install --all

Installs a ready-to-use SKILL.md into .claude/skills/. Ten skills, sources in ./skills/:

stateloom-setup, stateloom-docker, stateloom-design, stateloom-erd (the entity-relationship diagram beside the machines: tools, rules, check_links, the two notations), stateloom-live-loop, stateloom-render, stateloom-codegen, stateloom-rispec, stateloom-service, stateloom-tailnet.

The last two are the deployment pair: stateloom-service runs the hub and canvas as supervised systemd user units over the published packages — including the upgrade a bare systemctl restart cannot perform, because the units execute installed binaries rather than a checkout — and stateloom-tailnet publishes that pair on a private Tailscale tailnet as one named Service with two endpoints. An agent arriving on a fresh host can stand the whole loom up, and keep it up, from these alone.

Environment contract

STATELOOM_* is read first; the legacy SMCRAFT_* twin is honored as fallback, and scripts/live-loop.sh exports both so every generation of reader agrees.

Variable Default Note
STATELOOM_PROJECT_FILE ./statemachine.smdf.json Must be ABSOLUTE. Two processes resolving a relative path against two cwds is the defect this prevents.
STATELOOM_BRIDGE_HOST 127.0.0.1  
STATELOOM_BRIDGE_PORT 4599 The hub.
STATELOOM_BRIDGE_URL http://$HOST:$PORT  
STATELOOM_WEB_PORT 4598 The canvas.
NEXT_PUBLIC_STATELOOM_BRIDGE_URL mirrors BRIDGE_URL Inlined at web BUILD time, not runtime.
STATELOOM_MCP_HTTP_PORT — Serve the MCP over HTTP instead of stdio. Unset = stdio, the default.
STATELOOM_MCP_HTTP_HOST 127.0.0.1 Interface for MCP HTTP mode.
STATELOOM_MCP_TOKEN — Bearer token, REQUIRED in HTTP mode — the server refuses to start without it.
STATELOOM_MCP_ROOT — Confine the MCP’s active document to this directory.
STATELOOM_MCP_LOCK_PROJECT — 1 refuses set_project_file entirely.

4598 (canvas) and 4599 (hub) are the loom’s pair. scripts/live-loop.sh env|hub|web-build|web|mcp-line resolves all of it once and writes .env.smcraft-live.

Export naming

A browser download always, and --stamp / stamp: true on demand, produce [ep252--]<Machine>--<yyMMddHHmmss>.<ext> — the chronicle episode when the document lives under one, the machine’s own name, and a stamp to the second. Built once in bridge-protocol/src/exportName.ts, so every surface names files alike.

RISE Specifications

Lineage / KINSHIP

Upstream (Feeds From)

Downstream (Feeds Into)

Conceptual Bridges