SimWeave Integration Plan
This document maps the SimWeave
public API onto EdgeWeave nodes, lays out a phased implementation order,
and lists the demos we want to reproduce as .weave files.
Goals
- Make every SimWeave subsystem usable from the EdgeWeave canvas without writing Python.
- Keep the node naming predictable:
simweave_<class>for constructors,simweave_<class>_<verb>for methods,simweave_plot_*for viz. - Produce a small set of
.weaveexemplars that mirror the most instructive demos in the SimWeave repo, so users can open them as starting points. - Preserve a clean Python export path — every node carries a
codegen.pythonhint so an exported.weaveis a runnable Python script.
Mental model
SimWeave classes fall into a few execution roles. EdgeWeave's port graph maps onto these roles cleanly:
| SimWeave role | EdgeWeave node shape |
|---|---|
Construction (MassSpringDamper(...), Service(...)) | "Construct" node — fields → instance output |
Action (sw.simulate(...), env.run()) | "Action" node — inputs + fields → result |
Result (SimulationResult, MCResult) | Pass-through with summary preview |
Visualisation (sw.plot_*) | "Viz" node — always_preview=True, returns Plotly figure |
Recorder (QueueLengthRecorder) | Side-channel attached to an entity |
A SimEnvironment is a special case: it's a context, not a value, so
we treat it as the central hub node — entities register against it.
Subsystem coverage
simweave.continuous
Highest-priority subsystem for the first slice — visually compelling, single-entity demos, no factory functions, no recorders. Map:
| Class / function | Node | Inputs | Fields | Output |
|---|---|---|---|---|
MassSpringDamper | simweave_msd | — | m, c, k | system |
SimplePendulum | simweave_pendulum | — | g, L | system |
SeriesRLC | simweave_rlc | — | R, L, C | system |
ThermalRC | simweave_thermal_rc | — | R, C, T_inf | system |
TwoMassThermal | simweave_thermal_two_mass | — | params | system |
QuarterCarModel | simweave_quarter_car | — | params | system |
simulate(...) | simweave_simulate_continuous | system, optional x0 | t_start, t_end, dt, x0_string, method | SimulationResult |
ContinuousProcess | simweave_continuous_process | system | method, n_substeps | process (registers with env) |
Constructor signatures for SimplePendulum, SeriesRLC,
ThermalRC, TwoMassThermal, QuarterCarModel need to be
confirmed against SIMWEAVE_API.md before implementation — the
SKILL only confirms MassSpringDamper(m, c, k). Until verified,
ship simweave_msd only and stub the others as TODOs.
simweave.discrete
| Class | Node | Notes |
|---|---|---|
Queue(maxlen, name) | simweave_queue | sink-style buffer |
PriorityQueue(...) | simweave_priority_queue | priority sort key — text field with allow-list |
Service(capacity, buffer_size, next_q, default_service_time, rng, name) | simweave_service | next_q is a port (Queue input) |
ArrivalGenerator(interarrival, factory, target, rng, name) | simweave_arrival_generator | interarrival and factory are callables — see "callable inputs" below |
Resource, ResourcePool | simweave_resource, simweave_resource_pool | inputs to Service-like nodes |
EntityProperties | inline / hidden | usually built inside the factory function |
Callable inputs (open design issue). ArrivalGenerator.factory
and .interarrival are arbitrary Python callables. Three options
ranked by user friction:
- Distribution presets node: a
simweave_distributionnode with fieldskind={exponential|uniform|normal|deterministic}andparams={rate=...}. Output is the distribution callable. Used forinterarrivaland forEntityProperties.service_time. - Factory builder node:
simweave_entity_factorywith fields for common per-entity properties (service_timedistribution,priority, etc.). Output is afactory(env) -> Entity. - Code-string escape hatch: a
simweave_python_callablenode with a multi-line text field. Compiled once viaexec. Used only when the preset nodes don't cover the case.
Recommended initial cut: ship (1) + (2). Add (3) only if users hit walls.
simweave.agents & simweave.spatial
| Class / function | Node | Notes |
|---|---|---|
grid_graph(rows, cols, diagonal) | simweave_grid_graph | small, atomic |
Graph(...) (general) | defer to v2 — needs node/edge editor | |
Agent(graph, start_node, tasks, speed, heuristic, name) | simweave_agent | heuristic is a preset dropdown (manhattan/euclidean/chebyshev) |
a_star, dijkstra (function-only paths) | simweave_path | inputs: graph, start, goal |
tasks is a list of node identifiers. Use a text field of comma-
separated tuples ("7,11; 3,4") and parse client-side, or accept a
JSON list via an input port.
simweave.supplychain
| Class | Node |
|---|---|
InventoryItems(...) | simweave_inventory_items — fields are parallel lists |
Warehouse(inventory, name) | simweave_warehouse — input port for InventoryItems |
Keep InventoryItems simple in v1: parallel comma-separated text
fields per attribute (part_names, unit_cost, stock_level, etc.),
parsed in the handler. v2: a richer table-editor node.
simweave.mc
| Function | Node | Notes |
|---|---|---|
run_monte_carlo(replicate, n_runs, executor) | simweave_run_monte_carlo | replicate is a callable — needs the same design as discrete factories |
run_batched_mc(batched, n_runs, seed) | simweave_run_batched_mc | batched likewise |
MCResult.mean / std / quantile | simweave_mc_summary | input: MCResult, fields: which statistic + q for quantile |
Practical first cut: implement simweave_run_batched_mc paired with
a simweave_python_callable node so we can demo a 1-line
rng.normal(0, 1, size=(n, 100)) step without designing the full
factory builder.
simweave.viz
| Function | Node | always_preview |
|---|---|---|
plot_state_trajectories(result) | simweave_plot_state_trajectories | yes |
plot_phase_portrait(result, x_idx, y_idx) | simweave_plot_phase_portrait | yes |
plot_mc_fan(mc, times, percentiles) | simweave_plot_mc_fan | yes |
plot_queue_length(qrec) | simweave_plot_queue_length | yes |
plot_service_utilisation(urec) | simweave_plot_service_utilisation | yes |
plot_warehouse_stock(wrec) | simweave_plot_warehouse_stock | yes |
plot_agent_path(agent, graph) | simweave_plot_agent_path | yes |
theme registry (set_default_theme, register_theme) | settings panel, not a node |
Recorders (QueueLengthRecorder, ServiceUtilisationRecorder,
WarehouseStockRecorder) are side-channel entities. Each viz node
that consumes a recorder gets a sibling simweave_record_<thing>
constructor node.
simweave.units & simweave.currency
Out of scope for v1. These are value types used inside other nodes;
they don't need their own canvas presence except possibly a
simweave_money constructor for demo workflows that show FX
conversion. Defer.
Implementation phasing
Phase 1 — Continuous physics vertical slice ✅
Goal: a user can drag four nodes onto the canvas, wire them, hit Run, and see the mass-spring-damper response. Files:
projects/demo_project/modules/user_blocks/
simweave_continuous.py # simweave_msd, simweave_simulate_continuous,
# simweave_plot_state_trajectories,
# simweave_plot_phase_portrait
projects/demo_project/
simweave_msd.weave # canvas-ready exemplar
Demos reproduced:
demos/01_mass_spring_damper.py— direct mapping (simweave_msd.weave)- (stretch)
demos/14_viz_tour.py— once more viz nodes exist
Phase 2 — Continuous fan-out ✅
Constructor signatures confirmed against
simweave/continuous/systems/*.py (uploaded source). Nodes added in
backend/core/nodes/simweave/simweave_continuous.py:
simweave_pendulum—SimplePendulum(length, mass, gravity, damping)simweave_rlc—SeriesRLC(resistance, inductance, capacitance)simweave_thermal_rc—ThermalRC(thermal_resistance, thermal_capacitance, ambient_temperature, initial_temperature)simweave_thermal_two_mass—TwoMassThermal(C_core, C_sink, k_core_to_sink, R_sink_to_ambient, ambient_temperature, initial_core, initial_sink)simweave_quarter_car—QuarterCarModel(sprung_mass, unsprung_mass, suspension_stiffness, damping, tyre_stiffness)
Also fixed in this phase: the original simweave_msd used m/c/k
based on the SKILL example, but the source uses mass/damping/
stiffness. Field names and codegen hints updated; the
simweave_msd.weave exemplar was migrated to the new keys.
.weave exemplars (in projects/demo_project/):
simweave_msd.weavesimweave_pendulum.weavesimweave_rlc.weavesimweave_thermal_rc.weavesimweave_quarter_car.weave
TwoMassThermal doesn't have a dedicated exemplar yet — its larger
parameter set is better introduced via a documented walkthrough
rather than a bare canvas.
Phase 3 — Discrete vertical slice ✅ (initial)
Ships simweave_arrival_generator + simweave_service +
simweave_sink + simweave_discrete_run +
simweave_plot_queue_length + simweave_plot_service_utilisation.
Files:
projects/demo_project/modules/user_blocks/
simweave_discrete.py # all six nodes plus DiscreteSpec / DiscreteRunResult
projects/demo_project/
simweave_mm1.weave # canvas-ready M/M/1 exemplar
Recorders are folded into the producer block as boolean flags
(record_queue_length, record_utilisation) instead of separate
simweave_record_* nodes — keeps the canvas readable for the common
case. Standalone recorder nodes can be added later if a use case
needs them at non-default sample points.
Edge semantics: edges between discrete blocks denote entity
flow, not data — see the cross-cutting "Edge semantics" section
below. This deliberately mirrors the torch nodes' upstream port
pattern (backend/core/nodes/torch_nodes.py, the LayerSpec graph).
Demos reproduced:
- the M/M/1-ish queue from the SimWeave SKILL examples
(
simweave_mm1.weave)
Deferred to Phase 3.x:
simweave_priority_queue,simweave_resource,simweave_resource_pool- Branching / fan-out routing (a router block, or a
Servicesubclass with a per-entitynext_qselector)
Phase 4 — Agents on a graph
simweave_grid_graph, simweave_agent, simweave_path,
simweave_plot_agent_path. Demo: pick the simplest A*/dijkstra
showcase from demos/.
Phase 5 — Supply chain & Monte Carlo
simweave_inventory_items, simweave_warehouse,
simweave_record_warehouse_stock, simweave_plot_warehouse_stock.
Add simweave_run_batched_mc + simweave_plot_mc_fan for the
ensemble fan-chart demo.
Phase 6 — Polish
- Theme presets exposed in EdgeWeave Settings, mapped to
sw.set_default_theme(...). Money/ FX nodes if a demo asks for them.- Code-export tests: ensure
.weave → .pyround-trip runs without EdgeWeave installed.
Cross-cutting decisions
Optional dependency. SimWeave is registered as
simweave[viz] in backend/requirements.txt. Each node module
top-level imports SimWeave inside a try/except ImportError so the
EdgeWeave backend boots even when the package is absent — handlers
fail with a clear pip install simweave[viz] message at execution
time.
Plotly previews. Viz nodes return the figure as the output value
and a self-contained HTML preview via
fig.to_html(include_plotlyjs="cdn", full_html=False). This matches
the style of backend/core/nodes/plotly_nodes.py and works in the
existing preview div without any frontend changes. Codegen for viz
nodes should emit import simweave as sw + the corresponding
sw.plot_* call.
Stateful instances on the wire. Service, Warehouse, etc. carry
mutable state. EdgeWeave already passes Python objects between nodes
by reference — we rely on the engine's existing copy/cache semantics.
A worth-flagging risk: re-running a graph re-instantiates each
construction node, so previously-recorded state is dropped between
runs. This is the desired behaviour but should be called out in user
docs.
Categories. All SimWeave nodes use the SimWeave top-level
category in the sidebar, with sub-categories per subsystem rendered
via the category field as "SimWeave / Continuous",
"SimWeave / Discrete", etc.
Edge semantics — three flavours of connection. EdgeWeave today treats every wire as "data passes from upstream output to downstream input". As we add SimWeave (and revisit control-flow nodes for loops/conditionals), at least three semantically-distinct edge kinds emerge:
| Kind | Carries | Examples | Topo direction |
|---|---|---|---|
| Data flow (default) | A value (DataFrame, tensor, scalar) | Most existing nodes | Upstream produces, downstream consumes. |
| Execution / control flow | Nothing — signals "run this block before that block" | Torch LayerSpec upstream port; future for/while/if nodes | Upstream completes before downstream starts. |
| Entity flow | Conceptually "tokens / entities move from A to B" | SimWeave discrete blocks; future SimEvents-style nodes | Visual direction = entity direction; construction may run in reverse. |
Concrete handling today:
- Torch and SimWeave-discrete edges both pass a lightweight handle
(
LayerSpec/DiscreteSpec). The handle is technically a "data" payload, but the user-visible meaning is execution-order / entity-flow respectively. - SimWeave-discrete blocks defer SimWeave-object construction to the
terminal Run node, because SimWeave constructors expect downstream
refs (
Service(next_q=…),ArrivalGenerator(target=…)). The Run node topo-sorts sinks-first and constructs in dependency order.
Open follow-ups (not blocking either Phase 1 or Phase 3):
- Adopt a port-colour convention so users can tell the three edge kinds apart at a glance — e.g. data = grey, execution = orange, entity = blue.
- Audit the existing torch
upstreamport and the future for-loop / if-statement nodes to use a single sharedEXEC_PORT_KINDconstant. - Decide whether to surface a stricter "edge kind" annotation in the
node-meta dict (e.g.
inputs: [{"name": "upstream", "kind": "exec"}]) so the validator can refuse mixed-kind connections.
Open questions for the user
-
Constructor signatures.✅ Resolved — confirmed from the uploadedsimweave/continuous/systems/*.pysource. Phase 2 nodes landed. -
Factory function UX. Distribution-preset node + factory-builder node (recommended) versus a raw "compile this Python source" node (escape hatch). User leaves this to recommendation; provisional plan is the preset + builder pair, with the raw-Python node held in reserve for power users when a real demo demands it. Phase 3.x.
-
Theme integration.✅ Resolved — Plotly-only theme. Wire it through EdgeWeave Settings (panel control → backend config →sw.set_default_theme(...)called once during backend startup, re-applied on settings save). No per-graph theme node for now. -
Demo✅ Resolved —.weavelocation.projects/demo_project/for now; revisit when the example set grows large enough to warrant a dedicatedexamples/folder or an in-app picker.
Newly raised
- Worker cold-start cost. Each graph run spins a fresh subprocess
that imports every node module — incl. heavyweights like
torchthat the user's graph may not touch. Selective per-graph module loading via a build-time manifest is captured separately in Selective Module Loading. Out of scope for the SimWeave work itself, but the worst case (cold-start on a simplesimweave[viz]graph alongside a torch-loaded backend) is the kind of thing it would fix.
SimWeave skill / doc discrepancies
While building the Phase 1–3 nodes we had to verify several constructor
and method signatures against the uploaded simweave/**/*.py source.
A few details in the local SimWeave skill (~/.claude/skills/simweave/)
disagree with the source of truth. Recording them here so the skill can
be patched in one pass:
| Surface | Skill says | Actual source says | Notes |
|---|---|---|---|
MassSpringDamper.__init__ | m, c, k | mass, damping, stiffness, x0 | Long-form names; verified against simweave/continuous/systems/mass_spring_damper.py. The short-form m/c/k are not aliases — they raise TypeError. |
SimEnvironment.__init__ | skip_idle_gaps=False accepted | only start, dt, end, graph accepted | skip_idle_gaps is a parameter on env.run(until, skip_idle_gaps=False), not the constructor. Passing it to __init__ raises TypeError: __init__() got an unexpected keyword argument 'skip_idle_gaps'. Verified against simweave/core/environment.py. |
SimplePendulum.__init__ | (varies) | length, mass=1.0, gravity=9.81, damping=0.0, x0=None | Note gravity (full word), not g. |
SeriesRLC.__init__ | (varies) | resistance, inductance, capacitance, x0=None | Long-form names. |
ThermalRC.__init__ | (varies) | thermal_resistance, thermal_capacitance, ambient_temperature, initial_temperature | All long-form. |
Service.__init__ | (varies) | capacity=1, buffer_size=10, next_q="terminus", resources=None, default_service_time=1.0, rng=None, name=None | next_q defaults to the sentinel string "terminus". |
Queue.__init__ | (varies) | maxlen=10, name=None, next_q="terminus" | Note maxlen, not capacity. |
ArrivalGenerator.__init__ | (varies) | interarrival, factory, target, rng=None, name=None | interarrival is a callable taking an np.random.Generator; factory takes a SimEnvironment. |
When updating the SKILL.md, the two breaking discrepancies are the first two rows — those crashed our nodes outright. The rest are flagged for completeness so the skill matches the canonical source.
Codegen / export reliability (2026-08-12)
A dedicated pass (feature/codegen-export-coverage) actually exported
every shipped .weave demo through the real pipeline and executed the
generated script, not just eyeballed it or compiled it — several bugs
were syntactically valid Python that only failed at runtime. See
backend/tests/test_demo_exports.py for the permanent regression test
(parametrized over every file in projects/demo_project/*.weave).
Fixed, affecting SimWeave nodes directly:
simweave_arrival_generator/simweave_service/simweave_sink's"imports"baked in theimport/fromkeyword (same bug class as the numpy_io/image_read fix from the layers slice) — producedimport import simweave as sw.- Those same three nodes'
exprused{name!r}— the generator's substitution already appliesrepr()itself and never matches the literal substring{name}inside{name!r}, so the placeholder was left completely unsubstituted (a realSyntaxError, not just broken output). simweave_simulate_continuous'sexprwrotemethod='{method}'(manually quoted) — combined with the generator's ownrepr(), this produced double-quotedmethod=''rk4''. Also fixedx0_string(a CSV field like"1.0,0.0") being substituted as a single quoted-string array element instead of being parsed into actual numbers.
Known, deliberately unfixed (tracked as xfail in
test_demo_exports.py, each flagged as its own follow-up task rather
than patched speculatively):
- Discrete SimWeave can't export at all.
simweave_arrival_generatorneeds a downstreamQueueas itstarget=argument, which per-node codegen has no way to supply — this is exactly why the runtime handlers defer real object construction tosimweave_discrete_run's own sink-first traversal (seesimweave_discrete.py's module docstring). Fixing this needs the same deferred-construction trick reproduced in generated code, not a template patch.