Install
$ agentstack add skill-leventilo-mobius-simspec-author ✓ 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
simspec-author
Purpose and scope
This skill composes a single typed JSON document, the SimSpec, from artifacts produced by three upstream skills. It is the load-bearing handoff point of the Mobius DAG: every downstream agent reads the SimSpec and writes back into it. If the SimSpec is wrong, every primitive, every slider, every narration segment downstream is wrong.
What this skill does NOT do. It does not infer physics from a paper - that is physics-interpreter. It does not generate Python code for primitives - that is primitive-generator. It does not check unit homogeneity, CFL bounds, conservation residuals, or figure-diff - those are the five scripts of science-integrity. It does not invent numerical values that are not present in upstream artifacts. It is a typed-merge skill with explicit ambiguity logging, nothing more. Its place in the DAG is between the three input streams (paper-parser, physics-interpreter, claim-extractor) and the rest of the pipeline (primitive-generator, science-integrity, viz-mapper, paper-diff, narration).
Inputs
The skill expects three upstream artifacts on disk in the current working directory.
paper.json from paper-parser populates the paper.* subtree directly: arxivId, doi, title, authors, abstractHash, figures[]. Each figure carries a caption and a list of extracted_claims produced by Opus 4.7 vision over the figure bitmap.
physics.json from physics-interpreter populates the entire physics.* subtree: governing_equations[] with paired LaTeX and sympy strings, boundary_conditions[], initial_conditions[], constants[] annotated by source (paper, standard, derived), the regime enum, and the named approximations[] array.
claims.json from claim-extractor populates paper.numerical_claims[] and supplements figures[i].extracted_claims. Each claim ships a value, a UCUM unit, the section anchor, the extractor's self-rated confidence, and a verbatim source_quote for traceability.
Three additional fields the skill receives via orchestrator arguments rather than from upstream artifacts: a target viz.layout hint, a desired simulation.rng_seed, and an optional simulation.time.tmax ceiling. Any field not derivable from these inputs becomes an ambiguity (see below).
Output format (mandatory)
You MUST emit your final output as a single fenced ``json block at the END of your reply, with NO prose after the closing fence. The orchestrator parses that block by regex (/``(?:json)?\s*\n([\s\S]*?)\n\s*`/) and ignores everything else in your text content. Writing simspec.json to disk via code_execution is fine and recommended (downstream chained skills read it from /mnt/user-data/outputs/), but the fenced JSON IS the SimSpec artifact the orchestrator consumes.
The fenced JSON IS the full SimSpec object — schema reference: mobius/skills/simspec-author/schema/simspec.schema.json. Every top-level key required by the schema (paper, physics, primitives, simulation, integrity, viz, narration) MUST be present. Validate against the schema before emitting; if the validator is unavailable in the container, proceed AND append { "type": "schema-validation", "status": "pending", "message": "..." } to integrity.checks[] per the §"Validation procedure".
viz.scene_archetype is REQUIRED. The orchestrator passes the chosen archetype as upstream.archetype in your input envelope. You MUST copy that value verbatim into viz.scene_archetype on every emission, including refinement re-emits. Phase F's template router selects the renderer by scene_archetype; emitting a SimSpec without it ships a broken downstream. The orchestrator also patches the field post-Phase-C as defence-in-depth (idempotent), but do NOT rely on the patch — emit the field yourself.
Skeleton (every field MUST be filled per the composition rules below):
{
"paper": { "arxivId": "...", "doi": "...", "title": "...", "authors": [], "abstractHash": "...", "figures": [], "numerical_claims": [] },
"physics": { "governing_equations": [], "boundary_conditions": [], "initial_conditions": [], "constants": [], "regime": "linear", "approximations": [] },
"primitives": [],
"simulation": { "rng_seed": 0, "solver_hints": { "scheme": "...", "stability_constraint": "..." }, "time": { "t0": 0, "tmax": 1 } },
"integrity": { "checks": [], "overall": "ok" },
"viz": { "scene_archetype": "field-2d", "palette_map": {}, "sliders": [], "annotations": [], "camera_presets": [] },
"narration": { "what": "", "why": "", "try_it": "", "claims_referenced": [], "caveats": [], "segments": [] }
}
If the inputs are degenerate (missing physics.json, primitive without python_code, validator failure), emit a fenced JSON block carrying a single error field instead of an invalid SimSpec:
{ "error": "primitive 'fraunhofer' has empty python_code; refusing to author SimSpec until primitive-generator runs" }
DO NOT emit narration, summaries, or follow-up questions after the closing fence — they break the orchestrator's downstream consumption and get silently dropped.
Composition rules
Ten rules, in order of precedence. When two rules disagree, the lower-numbered rule wins.
Rule 1. Every primitive id must be a valid identifier and must be unique within primitives[]. Use kebab-case derived from the human-readable name: "Coherent laser source 632nm" becomes coherent-laser-632nm. Never reuse an id across primitives.
Rule 2. A primitive cannot be authored without python_code. If primitive-generator has not yet emitted code for a primitive, do not include the primitive. The orchestrator will re-invoke primitive-generator and call this skill again. Empty string is a schema violation.
Rule 3. If physics.regime is quantum-coherent and any governing equation has domain_hints containing QM, default simulation.solver_hints.scheme to FFT-spectral and stability_constraint to dt 1000 (heuristic: any parameters.{N,count,n_particles}.value > 1000), choose particle-cloud; else fall through to field-2d.
- If
primitives[]includes typelattice, ORphysics.regime == "stochastic"with a discrete-state primitive, chooselattice. - If every primitive is type
observablewithviz_hint.representation == "timeseries", choosegraph-time. - If every primitive is schematic-only (no numerical fields, only
source/operator/detectortypes with emptyparameters), chooseschematic.
Default fallback: field-2d. Every choice — including the fallback — is logged in integrity.checks[] with type: "archetype-routing", status: "pass" for a deterministic match and status: "warn" when the rule fired only after defaulting. The orchestrator surfaces archetype-routing warnings to the user as a "switch viz mode" toggle in the UI.
A viz_hint_archetype advisory field MAY be present on each physics.governing_equations[i] (emitted by physics-interpreter, see that skill §11). Treat it as a tie-breaker between two equally-matched rules above: when two archetypes are admissible, the most-frequent advisory across equations wins. Never override a hard rule with an advisory.
Ambiguity handling
The SINTEF tacit-assumption gap, reframed. Whenever composition requires a choice that is not derivable from the three input artifacts, the choice is committed AND logged as an explicit entry in integrity.checks[]:
{
"type": "ambiguity",
"status": "warn",
"message": "tmax not in inputs; derived 10*L/c = 1.2e-8 s from wave-propagation scaling (Rule 7).",
"details": { "rule": "Rule 7", "L": 0.36, "c": 3.0e8 }
}
Three rules govern this. First, never silently fall back to a default - every default emits a check entry. Second, the message field must name the composition rule by number, so a downstream reader can audit. Third, when two upstream artifacts conflict (e.g. physics.json says regime: linear but claims.json carries a claim that only makes sense in a nonlinear regime), the conflict itself is an ambiguity with status: "warn"; the spec proceeds with the physics-interpreter value because that skill is the physics authority, but the orchestrator sees the warning and may re-dispatch.
Validation procedure
Before returning the SimSpec, run the bundled validator (Phase 3 will land scripts/validate_simspec.py; for now the validator is invoked inline by the orchestrator with python -m jsonschema -i spec.json schema/simspec.schema.json). The validator wraps jsonschema.Draft202012Validator against schema/simspec.schema.json.
If the validator is not yet available in the agent's container, emit the JSON anyway and append:
{ "type": "schema-validation", "status": "pending", "message": "validator not available in this session" }
The orchestrator will re-validate at a later stage. Never block on missing validator infrastructure - the spec is still useful to downstream skills, and the explicit pending flag prevents silent corruption.
Canonical form
The spec must be stable under re-serialization so cache keys based on its bytes are reliable. Three rules.
The top-level object preserves the fixed key order [paper, physics, primitives, simulation, integrity, viz, narration]. Every nested object sorts its keys alphabetically. Arrays sort by id if present, else by name, else by their first scalar field (e.g. t_start for narration segments).
Floating-point values serialize with at most six significant digits. Integers serialize without a decimal point. Use json.dumps(spec, sort_keys=True, separators=(",", ":")) after applying the top-level key order, then pretty-print externally if needed.
Strings are NFC-normalized UTF-8; LaTeX backslashes are kept escaped per JSON. Trailing whitespace inside strings is stripped. The abstractHash is canonicalized to lowercase hex.
Worked examples
Five compact subtree examples, one per canonical paper class.
Young's double-slit (optics, wave)
{
"primitives": [
{
"id": "monochromatic-source",
"type": "source",
"name": "Monochromatic plane wave",
"parameters": {
"wavelength": { "value": 6.328e-7, "unit": "m", "range": [4.0e-7, 7.5e-7], "label": "wavelength" }
},
"viz_hint": { "representation": "2D-field", "palette_index": "c0" }
},
{
"id": "double-slit",
"type": "operator",
"name": "Double slit aperture",
"parameters": {
"slit_separation": { "value": 1.0e-4, "unit": "m", "range": [2.0e-5, 5.0e-4], "label": "slit separation d" },
"slit_width": { "value": 2.0e-5, "unit": "m", "range": [5.0e-6, 1.0e-4], "label": "slit width a" }
},
"viz_hint": { "representation": "2D-field", "palette_index": "c1" }
}
],
"simulation": {
"solver_hints": { "scheme": "FFT-spectral", "stability_constraint": "Nyquist-sampled aperture" }
},
"viz": {
"sliders": [
{ "param_path": "primitives[id=double-slit].parameters.slit_separation.value",
"label": "d", "min": 2.0e-5, "max": 5.0e-4, "step": 1.0e-6, "default": 1.0e-4, "unit": "m",
"affects": ["double-slit"] }
]
}
}
Simple pendulum (mechanics, ODE)
{
"primitives": [
{
"id": "pendulum-body",
"type": "particle",
"name": "Point mass on rigid rod",
"parameters": {
"length": { "value": 1.0, "unit": "m", "range": [0.1, 5.0], "label": "rod length L" },
"theta0": { "value": 0.2, "unit": "rad","range": [0.01, 0.3], "label": "initial angle" }
},
"viz_hint": { "representation": "phase-space", "palette_index": "c3" }
}
],
"physics": {
"regime": "weakly-nonlinear",
"approximations": ["harmonic-small-amplitude"]
},
"simulation": {
"solver_hints": { "scheme": "RK4", "stability_constraint": "dt = 1" }
}
}
2D Ising model (statistical mechanics)
{
"primitives": [
{
"id": "spin-lattice",
"type": "field",
"name": "Square spin lattice",
"parameters": {
"L": { "value": 64, "unit": "1", "range": [16, 256], "label": "lattice side" },
"J": { "value": 1.0, "unit": "1", "range": [0.1, 4.0], "label": "coupling J" },
"T": { "value": 2.3, "unit": "1", "range": [0.5, 5.0], "label": "temperature T/J" }
},
"viz_hint": { "representation": "2D-field", "palette_index": "c5" }
}
],
"physics": { "regime": "stochastic" },
"simulation": {
"solver_hints": { "scheme": "Monte-Carlo", "stability_constraint": "Metropolis detailed-balance" },
"rng_seed": 1742391384
}
}
Stokes flow past a sphere (fluid, low Re)
{
"primitives": [
{
"id": "uniform-far-field",
"type": "boundary",
"name": "Uniform far-field velocity",
"parameters": {
"U_inf": { "value": 0.01, "unit": "m.s-1", "range": [1.0e-4, 0.1], "label": "U_inf" }
},
"viz_hint": { "representation": "2D-field", "palette_index": "c3" }
},
{
"id": "sphere",
"type": "operator",
"name": "No-slip sphere",
"parameters": {
"R": { "value": 1.0e-3, "unit": "m", "range": [1.0e-4, 1.0e-2], "label": "radius R" }
},
"depends_on": ["uniform-far-field"],
"viz_hint": { "representation": "2D-field", "palette_index": "c1" }
}
],
"physics": {
"regime": "linear",
"approximations": ["Stokes", "incompressible", "Re-much-less-than-1"]
},
"simulation": {
"solver_hints": { "scheme": "analytic", "stability_constraint": "Re < 1" }
}
}
Interaction with other skills
physics-interpreter writes physics.json containing the governing equations, boundary conditions, initial conditions, constants, regime, and approximations. simspec-author reads this verbatim into the physics subtree; it does not reinterpret. If the interpreter omits a field that this skill needs (e.g. no regime), an ambiguity entry is emitted.
primitive-generator produces the Python code that fills primitives[i].python_code. simspec-author waits for that code before authoring the primitive. The orchestrator dispatches primitive-generator once per primitive id; this skill assembles the results.
science-integrity runs after this skill and writes back into integrity.checks[]. simspec-author seeds the array with any ambiguity warnings; science-integrity appends the five validator results. The overall field is computed by science-integrity, not here - this skill leaves it as "degraded" if any ambiguity was logged, else "ok".
viz-mapper reads viz.palette_map and viz.sliders[] and renders the Ciechanowski layout. It also reads viz.annotations[] to place arrows and dimension lines. simspec-author authors the slider list from the slider-eligible parameters under Rule 8.
narration reads narration.segments[] to render voice or captioned prose. simspec-author either passes through narration provided upstream, or emits a placeholder segment under Rule 10.
paper-diff reads paper.figures[] and the simulation output to compute perceptual hash distance. simspec-author does not author paper-diff results; those land in integrity.checks[] with type: "figure-diff".
Red flags
Refuse to generate when any of the following holds. Emit an integrity check with status: "fail" and return without writing the spec.
A primitive has no python_code. primitive-generator must run first; refusing protects downstream from executing empty modules.
A constant appears in physics.constants[] with source: "paper" but is absent from any paper.numerical_claims[] or any equation's variables map. Fabricated constants are forbidden; they are the single most common failure mode in early Mobius runs.
A slider exposes a parameter that, at any point in its range, would violate one of physics.approximations. Sliders are
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: leventilo
- Source: leventilo/mobius
- License: MIT
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.