simspec-author
Skill8 starsChanged 7 months agoMITView on GitHub
Compose a validated SimSpec from paper-parser, physics-interpreter, and claim-extractor outputs. Use when the orchestrator has upstream artifacts and needs a typed canonical spec before primitive-generator runs. The spec is validated against simspec.schema.json; ambiguities are explicitly logged.
In this repository9 other files it gives its agents.
- claim-extractorSkill
- narrationSkill
- paper-diffSkill
- paper-parserSkill
- physics-interpreterSkill
- primitive-generatorSkill
- science-integritySkill
- shader-devSkill
- viz-mapperSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. About 0 are common, 2 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 68
If the inputs are degenerate (missing physics.json, primitive without python_code, validator failure), emit a fenced JSON block carrying a …
- Distinctive: line 76
DO NOT emit narration, summaries, or follow-up questions after the closing fence — they break the orchestrator's downstream consumption and …
- Very specific: line 84
Rule 1: Every primitive id must be a valid identifier and must be unique within primitives[].
- Very specific: line 88
Rule 3: If physics.regime is quantum-coherent and any governing equation has domain_hints containing QM, default …
- Very specific: line 90
Rule 4: If any domain_hints includes wave or em and physics.regime is linear, default to FDTD for finite domains with absorbing boundaries, …
- Very specific: line 92
Rule 5: If any domain_hints includes mechanics and the highest derivative in any sympy expression is second-order in time, default to RK4 …
- Very specific: line 94
Rule 6: If physics.regime is stochastic, default to Monte-Carlo and require a non-zero rng_seed.
- Very specific: line 110
If primitives[] includes type medium with optical material AND physics.regime == "linear", choose glass-dispersion only when the simulation …
- Very specific: line 111
If primitives[] includes type field with viz_hint.representation == "2D-field", choose field-2d.
- Very specific: line 113
If primitives[] includes type particle and the count of particle-typed primitives' parameters suggesting N > 1000 (heuristic: any …
- Very specific: line 114
If primitives[] includes type lattice, OR physics.regime == "stochastic" with a discrete-state primitive, choose lattice.
- Very specific: line 115
If every primitive is type observable with viz_hint.representation == "timeseries", choose graph-time.
- Very specific: line 116
If every primitive is schematic-only (no numerical fields, only source/operator/detector types with empty parameters), choose schematic.
- Very specific: line 118
Default fallback: field-2d.
- Very specific: line 139
Before returning the SimSpec, run the bundled validator (Phase 3 will land scripts/validate_simspec.py.
- Distinctive: line 141
If the validator is not yet available in the agent's container, emit the JSON anyway and append:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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.jsonto 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 < hbar / (max|H| * 4). If the equation is the time-independent Schrodinger equation, default to analytic only when an analytic solution is named in physics.approximations (e.g. infinite-well, harmonic-oscillator).
Rule 4. If any domain_hints includes wave or em and physics.regime is linear, default to FDTD for finite domains with absorbing boundaries, FFT-spectral for periodic, and analytic only when physics.approximations includes paraxial or thin-lens.
Rule 5. If any domain_hints includes mechanics and the highest derivative in any sympy expression is second-order in time, default to RK4 for non-conservative systems and leapfrog for symplectic Hamiltonian systems (detected by the presence of a Hamiltonian constant or by physics.approximations listing symplectic).
Rule 6. If physics.regime is stochastic, default to Monte-Carlo and require a non-zero rng_seed. If the orchestrator did not supply one, set seed = int(paper.abstractHash[:8], 16) so the same paper always gets the same seed.
Rule 7. If simulation.time.tmax is unspecified, derive it from characteristic scales. For wave propagation use 10 * L / c where L is the longest extent and c the wave speed inferred from constants. For oscillation use 20 * T where T is the longest period detectable in the equations. For diffusion use L^2 / (4 * D). Always log the chosen rule in integrity.checks[] with type: "ambiguity", status: "warn".
Rule 8. A parameter is slider-eligible iff its range has strictly positive width AND it does not violate any approximation listed in physics.approximations. Example: under paraxial, do not expose an angle parameter beyond 0.3 rad even if the natural range is wider. Truncate the range and log an ambiguity entry.
Rule 9. viz.palette_map must cover at least three slots when primitives[] has three or more types. Assign palette slots in the order primitives first appear: c0 to the first source, c1 to the first medium, c2 to the first detector, then continue. If primitives have explicit viz_hint.palette_index values, those win. Conflicts (two primitives requesting the same slot) become an ambiguity entry.
Rule 10. narration.segments[] must cover the full simulation timeline without gaps and without overlaps. If upstream did not provide narration, emit a single intro segment from t0 to tmax with markdown: "(narration pending)" and a warn-level integrity check. Never silently invent narration text.
11. scene_archetype routing
The composition rules now include a routing decision: select the viz.scene_archetype value based on the dominant primitive type and physics regime. The chosen value drives viz-mapper's template selection and renderer choice; a wrong archetype here ships the wrong engine downstream.
Decision rules, in order of precedence (lower-numbered rule wins):
- If
primitives[]includes typemediumwith optical material ANDphysics.regime == "linear", chooseglass-dispersiononly when the simulation is 3D (simulation.space.dimensions == 3); else chooseray-optics. - If
primitives[]includes typefieldwithviz_hint.representation == "2D-field", choosefield-2d. - If
primitives[]includes typefieldwithviz_hint.representation == "3D-volume", chooseorbital-3d. - If
primitives[]includes typeparticleand the count of particle-typed primitives' parameters suggestingN > 1000(heuristic: anyparameters.{N,count,n_particles}.value > 1000), chooseparticle-cloud; else fall through tofield-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 < T/40" }
}
}1D infinite potential well (quantum)
{
"primitives": [
{
"id": "well-region",
"type": "medium",
"name": "Infinite square well",
"parameters": {
"width": { "value": 1.0e-9, "unit": "m", "range": [1.0e-10, 1.0e-8], "label": "well width L" },
"n": { "value": 1.0, "unit": "1", "range": [1, 6], "label": "energy level n" }
},
"viz_hint": { "representation": "2D-field", "palette_index": "c2" }
}
],
"physics": {
"regime": "quantum-coherent",
"approximations": ["infinite-well", "non-relativistic"],
"boundary_conditions": [
{ "region": "x=0", "type": "dirichlet", "value": "0", "variable": "psi" },
{ "region": "x=L", "type": "dirichlet", "value": "0", "variable": "psi" }
]
},
"simulation": {
"solver_hints": { "scheme": "analytic", "stability_constraint": "n integer >= 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 user-facing physics; offering an out-of-regime knob silently breaks the simulation. Truncate the range or remove the slider.
A viz_hint.palette_index collides with an existing role assigned in viz.palette_map for a different primitive type. Resolve by reassigning, not by overwriting.
paper.arxivId and paper.doi are both missing. The spec must be traceable to a source; refuse to author an untraceable simulation.
Any string field exceeds the schema's maxLength. Truncate upstream, do not silently drop characters here.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

