agentleFS
leventiloleventilo/mobius/skills/primitive-generator/SKILL.md

primitive-generator

Skill8 starsChanged 7 months agoMITView on GitHub

Generate runnable Python code for each primitive in a SimSpec — sources, media, detectors, operators, fields, observables. Output is validated by science-integrity before execution. Uses an indexed pattern library; falls back to LLM synthesis when no pattern matches. Run after simspec-author, before science-integrity.

  • Installs packages

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. About 0 are common, 4 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If python_code is already non-empty for a primitive, the skill skips it (idempotent re-runs are safe).

    line 28
  2. Very specific:

    If a primitive cannot be generated (validator fails three times in a row, match-key cannot be built, regime missing), emit it WITHOUT …

    line 76
  3. Distinctive:

    If the entire run is degenerate (no SimSpec, no primitives to generate), emit a single top-level error:

    line 88
  4. Distinctive:

    DO NOT emit narration, summaries, or follow-up questions after the closing fence — they break the orchestrator's downstream consumption and …

    line 95
  5. Very specific:

    If exactly one pattern matches, instantiate its template_python by substituting {{param_name}} placeholders with values from …

    line 116
  6. Distinctive:

    When no pattern matches, invoke Opus 4.7 with a structured prompt:

    line 122
  7. Very specific:

    Every generated Python primitive must satisfy the following.

    line 136
  8. Distinctive:

    Required functions: Every primitive MUST define exactly one of:

    line 162
  9. Very specific:

    After every primitive is generated, the skill calls scripts/validate_primitive.py::validate_primitive(primitive, simspec).

    line 206
  10. Very specific:

    Import audit: Every import and from ... import ...

    line 209
  11. Very specific:

    Parameter coverage: Every params["..."] key referenced by the code must exist in primitive.parameters with a unit.

    line 213
  12. Very specific:

    Never silently fall back to a wrong-domain pattern.

    line 385
  13. Very specific:

    Never skip the validation step.

    line 387

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

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

primitive-generator

1. Purpose and scope

This skill produces the executable artifact of every Mobius simulation: the Python code that fills primitives[i].python_code in the SimSpec. The SimSpec arrives from simspec-author with the physics, simulation, and primitives subtrees populated, but with python_code fields empty. This skill writes that code, primitive by primitive, then hands the SimSpec back to the orchestrator. science-integrity runs immediately afterward and gates execution by Managed Agents.

Three things this skill does NOT do. It does not invent physics — physics-interpreter already chose the regime, the equations, and the approximations. It does not validate units, CFL, or conservation — those are five separate scripts shipped by science-integrity. It does not orchestrate primitive ordering or DAG resolution — that is the orchestrator's job using primitives[i].depends_on.

The skill is stateless across primitives: each primitive is generated independently, and depends_on ordering is enforced by the caller, not by this skill. This decouples primitive generation from graph topology and makes the skill reusable by any future Mobius DAG variant.

2. Input

A full SimSpec object on disk as simspec.json, or passed inline by the orchestrator. Required state:

  • paper.* — only used for cache-key computation (abstractHash).
  • physics.regime, physics.approximations, physics.governing_equations[*].domain_hints — drive pattern matching.
  • primitives[*] — every entry has id, type, name, parameters, depends_on, viz_hint, but python_code is empty.
  • simulation.rng_seed — injected into every stochastic primitive.
  • simulation.solver_hints.scheme — used to disambiguate when multiple patterns could match.

If python_code is already non-empty for a primitive, the skill skips it (idempotent re-runs are safe).

3. Output

The same SimSpec object with python_code filled for every primitive. The skill also appends entries to integrity.checks[] of type ambiguity:

  • One pass-status entry per primitive that resolved via Tier 1 (pattern match), with details.pattern_id.
  • One warn-status entry per primitive that resolved via Tier 2 (LLM synthesis), with details.nearest_patterns[] for audit.
  • One fail-status entry per primitive that failed both tiers, with details.last_error from the validator.

The orchestrator decides whether to retry, escalate, or abort based on integrity.overall.

3.bis 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 intermediate files via code_execution to /mnt/user-data/outputs/` is fine, but the canonical artifact MUST be inlined as the final fenced JSON block of your reply.

The canonical artifact for primitive-generator (consumed by orchestrator.ts Phase C, which reads result.primitives[].python_code and merges it into simspec.primitives[]):

{
  "primitives": [
    {
      "id": "fraunhofer",
      "type": "fraunhofer_diffraction",
      "name": "Fraunhofer Double-Slit",
      "parameters": {
        "wavelength": { "value": 6.328e-7, "unit": "m" }
      },
      "python_code": "import numpy as np\n\ndef initialize(params):\n    ...\n",
      "viz_hint": { "representation": "2D-field", "palette_index": "c0" }
    }
  ]
}

Required per primitive: id (matches simspec.primitives[i].id) and python_code (non-empty, satisfies the runtime contract in §5). The other fields (type, name, parameters, viz_hint) MAY be passed through from the SimSpec for traceability — the orchestrator only merges python_code — but emitting them helps downstream debugging.

If a primitive cannot be generated (validator fails three times in a row, match-key cannot be built, regime missing), emit it WITHOUT python_code and with an explicit error field on that primitive entry:

{
  "primitives": [
    { "id": "fraunhofer", "error": "validator failed 3x: missing parameter 'lambda_m'" }
  ]
}

If the entire run is degenerate (no SimSpec, no primitives to generate), emit a single top-level error:

{ "error": "simspec.json missing; nothing to generate" }

DO NOT emit narration, summaries, or follow-up questions after the closing fence — they break the orchestrator's downstream consumption and get silently dropped.

4. Two-tier strategy

Tier 1 — pattern match (fast, deterministic, LLM-free)

For each primitive, build the match key:

key = (primitive.type, physics.regime, dominant_domain_hint, sorted(physics.approximations))

Walk every JSON file in patterns/. A pattern matches iff:

  • pattern.match.type == primitive.type
  • pattern.match.regime == physics.regime
  • pattern.match.technique is in the SimSpec's solver scheme OR is the empty string
  • pattern.match.approximations is a subset of physics.approximations

If exactly one pattern matches, instantiate its template_python by substituting {{param_name}} placeholders with values from primitive.parameters[param_name].value. If multiple patterns match, prefer the one with the longer approximations list (more specific wins). If none match, fall through to Tier 2.

The substitution is a strict text replacement: every placeholder must resolve to a parameter present in primitive.parameters, else the skill emits a fail integrity check and the primitive proceeds to Tier 2 with the missing-parameter error in context.

Tier 2 — LLM synthesis (fallback, cached)

When no pattern matches, invoke Opus 4.7 with a structured prompt:

  1. The runtime contract (§5) verbatim.
  2. The 2-3 nearest patterns (by Hamming distance over the match tuple) as few-shot examples.
  3. The primitive's full spec (id, type, name, parameters, viz_hint).
  4. The relevant physics subtree slice.
  5. The simulation.solver_hints and simulation.rng_seed.

Cache the result keyed by sha256(canonical_json(primitive_spec) || canonical_json(physics)) so identical primitives across re-runs return identical code. Cache lives at patterns/_cache/ (gitignored, populated at runtime).

Tier 2 outputs are subject to the same validator as Tier 1 outputs. If validation fails, the skill retries up to 3 times with the failure message appended to the prompt as a previous_attempt_failed block.

5. The runtime contract

Every generated Python primitive must satisfy the following. Violations are caught by validate_primitive.py (§7) before the code reaches Managed Agents.

Function shape. Define exactly one of:

def initialize(params: dict) -> dict:
    """Sources, initial conditions, fixed media. Called once at t=t0."""
    ...

def run(state: dict, params: dict, dt: float) -> dict:
    """Step the simulation forward by dt. Called once per frame."""
    ...

state is the cumulative simulation state, a JSON-serializable dict. params carries the primitive's parameters as a flat float-keyed dict. dt is the wall clock advance for this step.

Imports. Whitelist enforced statically:

  • Allowed: numpy, scipy, sympy, pint, mobius_runtime.
  • Allowed stdlib: math, dataclasses, typing.
  • Rejected: os, subprocess, socket, requests, urllib, http, ftp, pickle, eval, exec, __import__. Any rejected import is a hard validator fail.

Determinism. Stochastic primitives must read the seed from params["rng_seed"] and instantiate a numpy.random.Generator via np.random.default_rng(params["rng_seed"]). Never call numpy.random legacy functions, never call random.random(), never read time.time(). Context7 confirms default_rng is the canonical 2.x API; the legacy RandomState and module-level functions exist only for backward compatibility.

No I/O. No file reads except the runtime helper module. No network. No subprocess. The orchestrator wraps each generated python_code with a runner harness that calls initialize(params) or run(state, params, dt) and serializes the returned dict via print(json.dumps(...)). Your code MUST NOT contain bare print() calls — but it MUST return a dict from initialize or run. Diagnostics return inside the output dict under state["_diagnostics"]. Parameters arrive flattened from {value, unit, range, label} to scalar value (the harness extracts .value before calling your function), so write float(params["wavelength"]) not float(params["wavelength"]["value"]).

Required functions. Every primitive MUST define exactly one of:

  • initialize(params: dict) -> dict — for source-style primitives that emit initial conditions or fixed media. Called once at t=t0 with the flattened per-primitive parameters. Returns the seed state dict for downstream primitives.
  • run(state: dict, params: dict, dt: float) -> dict — for evolution-style primitives (operators, detectors, observables) that read merged upstream state, apply their transform, and return the updated state. Called once per frame. The orchestrator merges depends_on parents' return-value dicts into state before invoking, so anything an upstream primitive emitted is available here.

The orchestrator runs primitives in topologically-sorted depends_on order and chains return values through state. Two primitives at the same DAG level both see the same merged upstream state but DO NOT see each other's outputs.

Return shape. A JSON-serializable dict. Numpy arrays must be converted via .tolist() or, for arrays > 1000 elements, base64-encoded float32 buffers under the key state["<field>"] = {"shape": [...], "dtype": "f4", "buffer_b64": "..."}. The viz-mapper skill knows how to decode this.

Idempotence. run(state, params, dt) called twice in a row with the same (state, params, dt) must return numerically identical dicts (relative tolerance 1e-12). The validator smoke-tests this when no RNG is involved.

6. The mobius_runtime helper module

Shipped at patterns/_runtime/__init__.py. Generated primitives import from this module exclusively for shared physics — they never reinvent FFT, RK4, or Metropolis sweeps. The module is part of the skill bundle; Managed Agents pip installs it from a local wheel built at session start.

The helper signatures:

FunctionPurpose
gauss_2d(N, L, waist, x0, y0, amp)Returns complex (N, N) Gaussian beam profile on a square grid of side L. Centred at (x0, y0).
fft_propagate(field, z, lambda_m, paraxial=True)Fresnel angular-spectrum propagation by distance z. Direct port of wave-engine.js#propagateAngularSpectrum. Set paraxial=False for Rayleigh-Sommerfeld.
fraunhofer(field, z, lambda_m)Far-field FFT propagation, returns rescaled grid. Port of wave-engine.js#propagateFraunhofer.
rk4_step(rhs, y, t, dt)Standard 4th-order Runge-Kutta step. rhs(y, t) returns dy/dt.
monte_carlo_sample(prob_fn, n, rng)Rejection sampling on prob_fn, returns n samples. rng is a numpy.random.Generator.
schroed_split_step(psi, V, dt, dx, hbar, m)Split-step Fourier evolution of the time-dependent Schrödinger equation: half-step in V, full step in T (kinetic via FFT), half-step in V.
ising_metropolis_sweep(lattice, beta, J, rng)One Metropolis-Hastings sweep over a 2D Ising lattice, in-place.

The optics helpers (gauss_2d, fft_propagate, fraunhofer) are direct ports of the existing spike-visual/shared/wave-engine.js (399 LOC, FFT + Fresnel + Fraunhofer + masks). The pattern library prefers these over reinventing FFT — every optics primitive in §9 calls mobius_runtime rather than writing its own propagator.

The signatures are stable; bodies will land in Phase 3. Stubs ship with this skill (file: patterns/_runtime/__init__.py) so the import audit passes during dry-run.

7. Validation

After every primitive is generated, the skill calls scripts/validate_primitive.py::validate_primitive(primitive, simspec). The validator does six things:

  1. AST parse. python_code must be valid Python 3.12. A SyntaxError is fail.
  2. Import audit. Every import and from ... import ... statement is checked against the whitelist. Any rejected name is fail.
  3. Signature check. Exactly one of initialize(params) or run(state, params, dt) must be defined with the right argument names. Missing or extra positional args are fail.
  4. Idempotence smoke test. For non-stochastic primitives, the validator imports the code in a sandboxed namespace, calls the entrypoint twice with the primitive's parameters, and asserts numpy.testing.assert_allclose with rtol=1e-12. Mismatch is fail.
  5. Boundedness audit. Static AST scan for while True without break, recursion without depth limit, or unbounded list comprehensions. Each finding is a warn.
  6. Parameter coverage. Every params["..."] key referenced by the code must exist in primitive.parameters with a unit. Missing keys are warn (the LLM may have hallucinated a parameter; the orchestrator decides whether to add it to simspec-author's output).

The validator returns {"status": "pass"|"warn"|"fail", "issues": [...], "ast_summary": {...}}. The skill retries Tier 2 up to 3 times on fail; warn is reported but does not block.

8. Examples

Three fully-worked primitive generations covering source, medium, and detector. Each example shows: the SimSpec primitive entry, the matched pattern, the generated Python (post-substitution), and the integrity checks.

8.1 Source — Gaussian laser beam

SimSpec primitive entry:

{
  "id": "laser-source",
  "type": "source",
  "name": "Coherent Gaussian laser",
  "parameters": {
    "wavelength": { "value": 6.328e-7, "unit": "m",  "range": [4.0e-7, 7.5e-7], "label": "lambda" },
    "waist":      { "value": 8.0e-4,   "unit": "m",  "range": [1.0e-4, 2.0e-3], "label": "w0" },
    "grid_size":  { "value": 256,      "unit": "1",  "range": [128, 512],       "label": "N" },
    "extent":     { "value": 2.0e-3,   "unit": "m",  "range": [1.0e-3, 5.0e-3], "label": "L" }
  },
  "depends_on": [],
  "viz_hint": { "representation": "2D-field", "palette_index": "c0" }
}

Matched pattern. patterns/wave_gaussian_source.json. Match key ("source", "linear", "wave", ["paraxial"]) matches the SimSpec key with physics.regime == "linear", dominant domain_hint == "wave", and approximations == ["paraxial", "thin-lens"].

Generated Python (post-substitution):

import numpy as np
from mobius_runtime import gauss_2d


def initialize(params: dict) -> dict:
    N = int(params["grid_size"])
    L = float(params["extent"])
    waist = float(params["waist"])
    wavelength = float(params["wavelength"])
    field = gauss_2d(N=N, L=L, waist=waist, x0=0.0, y0=0.0, amp=1.0)
    intensity = (field.real ** 2 + field.imag ** 2)
    total_power = float(intensity.sum() * (L / N) ** 2)
    return {
        "field_re": field.real.astype(np.float32).tolist(),
        "field_im": field.imag.astype(np.float32).tolist(),
        "wavelength": wavelength,
        "extent": L,
        "grid_size": N,
        "total_power": total_power,
    }

science-integrity will check. Units (wavelength must be m, waist m); CFL is N/A here (no time stepping); conservation: total_power returned for downstream comparison; AST parse + import audit pass.

8.2 Medium — thin lens

SimSpec primitive entry:

{
  "id": "converging-lens",
  "type": "medium",
  "name": "Thin converging lens",
  "parameters": {
    "focal_length": { "value": 0.05, "unit": "m", "range": [0.01, 0.5], "label": "f" }
  },
  "depends_on": ["laser-source"],
  "viz_hint": { "representation": "2D-field", "palette_index": "c1" }
}

Matched pattern. patterns/wave_thin_lens.json. Match ("medium", "linear", "wave", ["thin-lens", "paraxial"]).

Generated Python:

import numpy as np
from mobius_runtime import fft_propagate


def run(state: dict, params: dict, dt: float) -> dict:
    N = int(state["grid_size"])
    L = float(state["extent"])
    wavelength = float(state["wavelength"])
    focal_length = float(params["focal_length"])
    field = np.array(state["field_re"], dtype=np.float64) + 1j * np.array(state["field_im"], dtype=np.float64)
    k = 2.0 * np.pi / wavelength
    dx = L / N
    coords = (np.arange(N) - N / 2) * dx
    xx, yy = np.meshgrid(coords, coords, indexing="xy")
    phase = -k * (xx ** 2 + yy ** 2) / (2.0 * focal_length)
    field = field * np.exp(1j * phase)
    field = fft_propagate(field, z=focal_length, lambda_m=wavelength, paraxial=True)
    return {
        **state,
        "field_re": field.real.astype(np.float32).tolist(),
        "field_im": field.imag.astype(np.float32).tolist(),
    }

science-integrity will check. Units (focal_length m); paraxial-approximation guard (waist << focal_length); conservation: |field|^2 integral preserved within 1e-6 (the lens is a phase mask, must conserve power); idempotence across two calls with same state.

8.3 Detector — fluorescence screen with saturation

SimSpec primitive entry:

{
  "id": "fluo-screen",
  "type": "detector",
  "name": "Fluorescence screen with saturation",
  "parameters": {
    "saturation_intensity": { "value": 1.0e3, "unit": "1", "range": [10, 1.0e5], "label": "I_sat" },
    "quantum_efficiency":   { "value": 0.85,  "unit": "1", "range": [0.1, 1.0],  "label": "QE" }
  },
  "depends_on": ["converging-lens"],
  "viz_hint": { "representation": "2D-field", "palette_index": "c2" }
}

Matched pattern. patterns/wave_screen_intensity.json. Match ("detector", "linear", "wave", []) — empty approximations subset matches anything.

Generated Python:

import numpy as np


def run(state: dict, params: dict, dt: float) -> dict:
    field_re = np.array(state["field_re"], dtype=np.float32)
    field_im = np.array(state["field_im"], dtype=np.float32)
    intensity = field_re ** 2 + field_im ** 2
    I_sat = float(params["saturation_intensity"])
    QE = float(params["quantum_efficiency"])
    response = QE * intensity / (1.0 + intensity / I_sat)
    return {
        **state,
        "screen_response": response.tolist(),
        "screen_max": float(response.max()),
        "screen_total": float(response.sum()),
    }

science-integrity will check. Saturation monotonicity (response strictly increasing in intensity, asymptote QE * I_sat); units (I_sat, QE dimensionless); no NaNs in response.

9. Interaction with other skills

simspec-author produces the input. This skill never modifies the physics, paper, or simulation subtrees — only primitives[i].python_code and integrity.checks[].

science-integrity runs immediately after this skill on every primitive's generated code. It performs the heavier physical checks (unit homogeneity via pint, CFL bounds, conservation residuals over a 10-step dry run, figure-diff once execution completes, and claim-match against paper.numerical_claims[]). The validator inside this skill is intentionally cheaper — its job is to filter out malformed code before science-integrity wastes compute on it.

viz-mapper reads the primitive's viz_hint and the shape of the returned dict. It does not call primitive-generator directly; it consumes the runtime output streamed by Managed Agents.

The orchestrator dispatches this skill once per primitive in the SimSpec, in the order resolved from depends_on. Multiple invocations in parallel are safe because each primitive is generated independently.

10. Red flags

Refuse to generate when any of the following holds. Emit an integrity check with status: "fail" and return without writing code.

A primitive's physics.regime is missing. The match key cannot be built; the orchestrator must re-invoke physics-interpreter.

A pattern's template_python references a placeholder that does not exist in primitive.parameters. The pattern is buggy; do not silently fall back to a different pattern, fail loudly so the pattern library can be fixed.

The generated code passes the validator but the import audit flagged a warn for an unrecognized import. Recognized imports (numpy, scipy, sympy, pint, mobius_runtime, math, dataclasses, typing) are explicit; anything else is rejected even if it appears innocuous (requests, os.path, pathlib).

A Tier 2 (LLM-synthesized) primitive failed the validator three times in a row. Stop retrying. Emit fail with the last validator output and let the orchestrator decide whether to escalate to a domain expert agent or downgrade the primitive's role.

The generated code calls random.random(), numpy.random.rand(), or time.time(). These break determinism and are forbidden even though they technically pass the import audit (the names are bound to whitelisted modules). The validator's static scanner treats these as fail.

Never silently fall back to a wrong-domain pattern. If the only matching pattern has a different domain_hint than the primitive's physics, prefer Tier 2 LLM synthesis with the mismatched pattern as a negative example (do_not_use_this_pattern_because: ...) rather than emitting incorrect code.

Never skip the validation step. Even if all eight Tier-1 patterns match cleanly across a SimSpec, every generated primitive runs through validate_primitive.py before the SimSpec is returned to the orchestrator. The cost is sub-millisecond per primitive; the bug surface saved is enormous.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.