# Simspec Author

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

- **Type:** Skill
- **Install:** `agentstack add skill-leventilo-mobius-simspec-author`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [leventilo](https://agentstack.voostack.com/s/leventilo)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [leventilo](https://github.com/leventilo)
- **Source:** https://github.com/leventilo/mobius/tree/main/skills/simspec-author

## Install

```sh
agentstack add skill-leventilo-mobius-simspec-author
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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):

```json
{
  "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:

```json
{ "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[]`:

```json
{
  "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:

```json
{ "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)

```json
{
  "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)

```json
{
  "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)

```json
{
  "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)

```json
{
  "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](https://github.com/leventilo)
- **Source:** [leventilo/mobius](https://github.com/leventilo/mobius)
- **License:** MIT

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

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-leventilo-mobius-simspec-author
- Seller: https://agentstack.voostack.com/s/leventilo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
