Install
$ agentstack add skill-leventilo-mobius-primitive-generator ✓ 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 Used
- ● Filesystem access Used
- ● Shell / process execution Used
- ✓ 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
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 hasid,type,name,parameters,depends_on,viz_hint, butpython_codeis 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), withdetails.pattern_id. - One
warn-status entry per primitive that resolved via Tier 2 (LLM synthesis), withdetails.nearest_patterns[]for audit. - One
fail-status entry per primitive that failed both tiers, withdetails.last_errorfrom 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.typepattern.match.regime == physics.regimepattern.match.techniqueis in the SimSpec's solver scheme OR is the empty stringpattern.match.approximationsis a subset ofphysics.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:
- The runtime contract (§5) verbatim.
- The 2-3 nearest patterns (by Hamming distance over the match tuple) as few-shot examples.
- The primitive's full spec (id, type, name, parameters, viz_hint).
- The relevant
physicssubtree slice. - The
simulation.solver_hintsandsimulation.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[""] = {"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:
| Function | Purpose | | --- | --- | | 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:
- AST parse.
python_codemust be valid Python 3.12. ASyntaxErrorisfail. - Import audit. Every
importandfrom ... import ...statement is checked against the whitelist. Any rejected name isfail. - Signature check. Exactly one of
initialize(params)orrun(state, params, dt)must be defined with the right argument names. Missing or extra positional args arefail. - 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 assertsnumpy.testing.assert_allclosewithrtol=1e-12. Mismatch isfail. - Boundedness audit. Static AST scan for
while Truewithoutbreak, recursion without depth limit, or unbounded list comprehensions. Each finding is awarn. - Parameter coverage. Every
params["..."]key referenced by the code must exist inprimitive.parameterswith a unit. Missing keys arewarn(the LLM may have hallucinated a parameter; the orchestrator decides whether to add it tosimspec-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. `pa
…
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.