Install
$ agentstack add skill-wolframresearch-system-modeler-ai-toolkit-annotate-modelica-plots ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Annotate Modelica Plots
This skill writes the spec-standard result-plot annotation into a Modelica .mo file so a model carries its own simulation plots:
annotation(Documentation(figures = {
Figure(identifier = "response", title = "Mass response", preferred = true,
plots = {
Plot(title = "Position and velocity",
curves = {Curve(x = time, y = s, legend = "position"),
Curve(x = time, y = v, legend = "velocity")},
x = Axis(label = "time"), y = Axis(label = "states", unit = ""))})}));
It is a self-contained source transform: Claude assembles a figures spec (JSON) — from the user's request, or from variables discovered by simulating — and the Python engine renders it to valid Modelica and splices it into the class annotation. The engine is pure Python (standard library only); WSMKernelX is used only afterwards as a validation gate.
Wolfram System Modeler renders this standard annotation natively from the stored figure, picking up the Plot.title, Axis.label, and per-curve legend. No vendor-specific annotation is needed.
It is idempotent: a class that already has figures = is skipped unless you pass --force (which strips the old figures assignment and regenerates). By default it prints a dry-run diff; nothing is written until --write.
> --force is destructive — ask the user first. It deletes the class's existing figures > block before regenerating, so any hand-written or previously curated plot definitions there are > lost (a user may have tuned those by hand). Don't pass --force on a class that already carries > figures without first confirming with the user that discarding them is intended. Review the > dry-run diff (no --write) to see exactly what would be removed.
The figures spec (JSON)
Shaped after the spec records (MLS annotations.tex §figure-plot-properties). Only fields you set are emitted:
{
"figures": [
{
"title": "Mass response", "identifier": "response", "preferred": true,
"caption": "Damped oscillation of %[position](variable:s).",
"plots": [
{
"title": "Position and velocity",
"x": {"label": "time"},
"y": {"label": "states", "unit": "", "scale": "Linear"},
"curves": [
{"x": "time", "y": "s", "legend": "position"},
{"x": "time", "y": "v", "legend": "velocity"}
]
}
]
}
]
}
Rules the engine enforces (from the spec):
Curve.x/Curve.ymust be result-references: a scalar variable (s,mass.v),time, or
der(v[, n]) — not arbitrary expressions. A bad reference is a hard error. x defaults to "time"; set it to a variable for an X vs. Y plot (e.g. {"x": "s", "y": "v"} for a phase portrait).
Figure.identifiermust be unique in the spec;Plot.identifierunique within its figure.- Axis
scaleis"Linear"(default),"Log", or{"Log": 10}to set the base. %{var}variable replacements and%[text](variable:ref)caption links are passed through; the
engine does not invent them.
To target a specific class in a multi-model file, either pass --class , or use a mapping spec {"Oscillator": {"figures": [...]}, "Plain": {"figures": [...]}}.
Running the annotator
The annotator lives in the PlotAnnotate/ package next to this file. Run it as a module from this skill's directory so the package is importable:
cd ""
python3 -m PlotAnnotate.main --file "" --analyze
Use python instead of python3 on Windows if that's what's on PATH. Only --file (and --spec, --vars-file) must be absolute or correctly-relative paths.
Cross-platform launcher (for simulate / validate steps)
> The simulate/validate steps below call the shared scripts/wsm_run.py and > scripts/plot_mat.py. Read [the shared-conventions appendix at the end of this file](#appendix-shared-conventions-for-the-modelica-skills) > (launcher resolution, shell rules, temp dir, JSON-array output, MSL 4.x notes) > before those steps. In a normal install the launcher is ../scripts/wsm_run.py > (` is the shared scripts/` folder); if that path doesn't exist, > see [Appendix → Locating the launcher](#locating-the-launcher).
Workflow
1. Identify the model file and target class
The user gives a .mo path. Pick a concrete instantiable model for any kernel step (Package.Model, not the package) — see [Appendix → Picking the model name](#picking-the-model-name).
2. Analyze — see classes and existing figure status
python3 -m PlotAnnotate.main --file "" --analyze
Lists every class, flagging has Documentation / HAS figures. Present this and confirm scope.
3. Choose what to plot
Pick curves that reveal emergent / non-obvious behavior — a dynamic response, a comparison, a phase shift, a limit case. Skip tautological curves that just re-trace a known input signal (if you'd see the same shape by plotting the input alone, it teaches nothing).
Curves default to x = time, but Curve.x can be any result variable, giving an X vs. Y plot. Reach for one when the relationship between two variables is the story: a phase portrait of an oscillator (limit cycles show as closed loops), a hysteresis loop, a transfer or force- displacement characteristic. Put an X vs. Y curve in its own Plot (not mixed with time-based curves — they'd share the x axis) and label both axes.
Mode A — user-specified. The user already knows the curves. Build the spec JSON directly from their request and skip to step 4.
Mode B — simulate-and-suggest. When the user doesn't name variables, discover them:
# simulate to produce a .mat (and, beside it, a .sim init file with the same stem)
python3 "/wsm_run.py" --mode simulate --model "" --name "" --timeout 180
# enumerate result variables (write working files next to the model, not into the skill dir)
python3 "/plot_mat.py" "/_wsm_simulate_temp/_res.mat" --list > "/vars.txt"
# get a starting spec (filters time/der/aux; one plot, x=time). Pass the .sim so protected
# variables are dropped — they are stored in the .mat but render blank in a stored figure.
python3 -m PlotAnnotate.main --file "" --vars-file "/vars.txt" \
--sim-file "/_wsm_simulate_temp/_res.sim" --suggest --max-curves 8 > "/figs.json"
The .sim file sits next to the .mat with the same stem (swap the .mat extension for .sim). Passing --sim-file is what stops protected variables from being suggested — without it, a protected variable in the result is offered as a curve and then renders blank in System Modeler.
The suggestion is a starting point — refine figs.json: drop parameters/noise, split into multiple plots by physical quantity, set titles/legends/axis units. Passing --vars-file to the --annotate step also warns when a curve references a variable absent from the result; passing --sim-file there additionally warns when a curve references a protected variable.
3b. Title, caption, and reference alignment
Every figure should be self-identifying and self-explaining:
- Title — name the quantity shown, not "Plot 1" (e.g.
"Refrigerant pressure"). The title is
how the user finds the plot in the model's figure list, so make it scannable.
- Caption — set a one-line
captionon everyFiguresaying what it shows and what to look
for (e.g. "Pressure rises with the accumulation after the step"). It renders beneath the plot in System Modeler. Use %[label](variable:ref) to link a variable inside the caption.
- Default — mark exactly one figure
preferred = trueso it opens by default. - Reference alignment — if the model implements a published reference (a paper, app note,
textbook) and that reference numbers its figures, name the model's figures to match it ("Fig. 6 - Stored refrigerant mass") and state the correspondence in the caption ("compare Fig. 6 of "). This makes a replication auditable at a glance — anyone can line the stored plot up against the source. When no reference is given, use your own clear, ordered scheme (e.g. "Evaporator 1 - Outlet vapour quality").
4. Preview — dry-run diff
python3 -m PlotAnnotate.main --file "" --class "" --spec "/figs.json" --annotate
Shows the unified diff without writing. --vars-file "/vars.txt" adds membership warnings, and --sim-file "/_wsm_simulate_temp/_res.sim" warns on any curve that references a protected variable. --force regenerates over an existing figures block.
5. Apply — write in place
python3 -m PlotAnnotate.main --file "" --class "" --spec "/figs.json" --annotate --write
Re-running without --force is a no-op for already-annotated classes (idempotent).
6. Validate — confirm the model still flattens (validate-modelica gate)
Annotations must not change the flatten result.
python3 "/wsm_run.py" --mode validate --model "" --name "" --timeout 90
Parse _wsm_validate_temp/validate.out.json (a JSON array — take [0]; field reference: [Appendix → Reading the JSON output](#reading-the-json-output)) and check status.flatten == "Pass". If it passed before annotating but fails after, that's a bug in the annotation, not the user's model — report it. Then remove the temp dir.
Notes and edge cases
- Pick an instantiable model for the kernel steps, not the package — use a nested model's full
dotted name. See [Appendix → Picking the model name](#picking-the-model-name).
figuresis inherited. A class's figures = its own plus those from base classes. Keep
Figure.identifiers unique across that whole collection (the engine checks within one spec; it can't see base-class figures, so choose distinct identifiers when extending).
- Result-references only. To plot a derived quantity, add a variable for it in the model and
reference that — Curve.y cannot be an expression like s + v.
- No protected variables in curves. System Modeler stores protected variables in the result
.mat (so plot_mat.py --list shows them), but a stored figure resolves curves against the public result tree only — a curve on a protected variable renders blank, and the model still flattens, so the validate gate won't catch it. The .sim file beside the .mat marks them (protected="true"); pass it as --sim-file to --suggest/--annotate so they are dropped and flagged. If you must show a protected quantity, expose it through a public variable in the model.
- X vs. Y (parametric) curves are supported.
Curve.xdefaults totime, but any
result-reference works (Curve(x = x, y = y)), and System Modeler renders the variable-vs-variable curve natively. Same rules as y: a result-reference (no expressions), and a protected variable renders blank on either axis. Don't mix time-based and X-vs-Y curves in one Plot — all curves share the plot's x axis (the engine warns on this); use separate plots. Label both axes, since the default "time" x-label no longer applies.
- Units. A non-empty
Axis.unitmust be compatible with the plotted variable's unit, or the
model won't flatten; leave unit = "" to let the tool choose per-curve units.
- Idempotency / regen. Default runs never duplicate a
figuresblock; use--forceto
regenerate after editing the spec — but it discards the existing figures (hand-written included), so confirm with the user first (see the caution near the top).
- MSL dialect (this toolchain ships MSL 4.x). Annotations don't affect flattening, but if the
model uses 3.2 names it fails the gate for unrelated reasons — see [Appendix → MSL 4.x dialect](#msl-4x-dialect).
Appendix: shared conventions for the Modelica skills
> Shared by every Modelica skill that drives WSMKernelX through the > bundled launcher; inlined here at release time. For the CLI/option > reference, environment variables (WSM_HOME, WSM_VSDEVCMD), > install discovery, and the analysis scripts, see > [../scripts/README.md](../scripts/README.md).
Locating the launcher
` (used throughout the skills) is the shared scripts/` folder. Some installs symlink the skill directories without it, so resolve it in this order and use the first that exists:
$WSM_SKILLS_SCRIPTS(bash) or$env:WSM_SKILLS_SCRIPTS(PowerShell), if set.../scriptsrelative to the skill directory — in a normal install
../scripts/wsm_run.py already exists, so use that path directly; do not run a shell probe to "resolve" it.
- The repo checkout you installed from, e.g.
.../agentskills/scripts. - Last resort, search the home directory:
- PowerShell:
Get-ChildItem $HOME -Recurse -Filter wsm_run.py -ErrorAction SilentlyContinue | Select-Object -First 1 - bash/zsh:
find ~ -name wsm_run.py -path '*scripts*' 2>/dev/null | head -1
If only #4 finds it, the install is missing the scripts/ link — tell the user to run install.sh (or install.ps1) from the repo, which links scripts/ too.
Shell and Python
On Windows, use PowerShell. The Git-Bash/cygwin layer may be broken (even ls/find can be absent, giving a misleading "exit 127 / command not found"). Run wsm_run.py with python (not python3); those calls are single-line and shell-agnostic. For cleanup use Remove-Item -Recurse -Force, not rm -rf. On macOS/Linux any POSIX shell is fine and python3 is the usual name.
Let the launcher own .mos/.bat and paths
Do not hand-write .mos scripts, .bat files, or hardcode install/compiler paths. The bundled scripts/wsm_run.py handles every OS difference — it finds the System Modeler install and kernel binary (macOS / Windows / Linux), finds and loads the right MSL files, generates the .mos, and runs the kernel with a working compiler environment per platform (system clang/gcc on macOS/Linux; the Visual Studio dev environment via VsDevCmd.bat on Windows). See [../scripts/README.md](../scripts/README.md) for WSM_HOME, the Windows compiler prerequisites, and the full option table.
When the install or compiler isn't found
The launcher searches each OS's standard install locations. If it prints ERROR: Could not locate a Wolfram System Modeler installation, the install is in a non-standard place — ask the user for it and re-run with --wsm-home "" (or have them set WSM_HOME).
Building and simulating also need a C++ toolchain:
- Windows: Visual Studio Build Tools. The launcher locates
VsDevCmd.bat
itself; if it reports the compiler environment is missing, pass --vsdevcmd "" (or set WSM_VSDEVCMD) and make sure Build Tools are installed.
- macOS: the Xcode command-line tools (
xcode-select --install). - Linux: gcc/g++.
Run python3 "/wsm_run.py" --mode info to see what the launcher discovered.
Temporary directories
The launcher works in a _wsm__temp/ directory next to the .mo file (_wsm_validate_temp/, _wsm_simulate_temp/, _wsm_diagnose_temp/) and leaves its outputs there. Tell the user, e.g.: "Working in temporary directory _wsm__temp/. This will be deleted afterwards." Pass --tempdir to reuse one directory across models in a session.
Clean up by removing the whole directory — use the user's shell:
rm -rf "/_wsm__temp" # macOS / Linux
# PowerShell: Remove-Item -Recurse -Force "\_wsm__temp"
Picking the model name
- The user may provide a path to a
.mofile, or you may already be working with
one in context.
- Extract the model name: the identifier after
modelon the first non-comment
line, e.g. model FooBar → FooBar. The filename does not always match the model name — parse the actual model/package declaration.
- For packages or nested models, use the top-level model name.
- Pick an instantiable model, not a package, for any kernel call. A
package
cannot be validated or simulated ("Invalid insta
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: WolframResearch
- Source: WolframResearch/system-modeler-ai-toolkit
- License: MIT
- Homepage: https://www.wolfram.com/system-modeler/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.