AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Simspec Author

skill-leventilo-mobius-simspec-author · by leventilo

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.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-leventilo-mobius-simspec-author

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-leventilo-mobius-simspec-author)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Simspec Author? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 type lattice, OR physics.regime == "stochastic" with a discrete-state primitive, choose lattice.
  • If every primitive is type observable with viz_hint.representation == "timeseries", choose graph-time.
  • If every primitive is schematic-only (no numerical fields, only source/operator/detector types with empty parameters), choose schematic.

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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.