Skip to content
Back to skills

Mdp Build

ASecurity

Stages 1–2 of the MDP pipeline: generate the spec-conformant domain (_uncertainty/_scenarios/_mdp/_gym, adapter, tests, campaign docs) from a frozen IR, gated by conformance + laws + differential. Use when {name}.signoff.json exists beside the schema and the domain code does not, or must be regenerated. Full pipeline: mdp-solver.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 9, 2026
businesspythongobashexpressawsgit

Works with

  • claude code
  • cli

Security analysis

A100/100

Scanned October 9, 2026

npx -y skills add tong-wang/auto-mdp-solver --skill mdp-build --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mdp Build?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mdp Build
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tong-wang-mdp-build/badge)](https://www.skillsdirectory.com/skills/tong-wang-mdp-build)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mdp-build
description: >
  Stages 1–2 of the MDP pipeline: generate the spec-conformant domain
  (_uncertainty/_scenarios/_mdp/_gym, adapter, tests, campaign docs) from a
  frozen IR, gated by conformance + laws + differential. Use when
  {name}.signoff.json exists beside the schema and the domain code does not,
  or must be regenerated. Full pipeline: mdp-solver.
---

# mdp-build — Stages 1–2 (IR → domain code + gym)

One op of the split MDP pipeline; `mdp-solver` is the conductor. The governing
docs are at `${CLAUDE_SKILL_DIR}/../mdp-solver/` — the `mdp-solver` sibling
of this skill's own directory (`${CLAUDE_SKILL_DIR}` is substituted by Claude
Code at load; on Codex it is the path printed beside this skill in the skills
list) — read them from that path, never from a filesystem search (a dev
checkout on disk would silently substitute unreleased content). Read `CONTRACTS.md` there first: it maps the
ops and says what each reads. **This op needs:** spec `MDP_PROJECT_SPEC.md`
§1–§7 (consult it while writing each file; do not code from memory of it),
`MDP_IR_SAMPLE.md`, the two `DOMAIN_*_TEMPLATE.md` files,
`examples/MANIFEST.md` and the one exemplar matched below — not spec §8–§14,
not the escalation guide. Everything else in that directory is consulted only
when a step below names it. Venv and run discipline per `ENVIRONMENT.md`
(retry budgets, background runs).

**Entry gate — run first, exit 0 required:**

```bash
<python> -m mdp_stage {domain} --for build
```


Then read `{domain}/CLAUDE.md`, the folder's operating brief: Claude Code
pushes it when work touches the folder, other hosts do not, and its hard
rules bind this op either way.

Never start from a prior op's (or a human's) report that the IR is ready —
the gate re-derives it: schema validates, `unconfirmed()` empty, sign-off
present and its fingerprint still current. A blocked entry goes back to
`mdp-formalize`, not around the gate.

Pick few-shot exemplars by problem shape. Three ship with this plugin in the
`examples/` directory of the `mdp-solver` skill (path above; see
`examples/MANIFEST.md` there):
`inv_single` vector state + two-step advance + episode-support demand +
exact-DP baseline; `mab` exploration/exploitation over an equivariant state +
censored feedback + a fitted rule scored as a benchmark; `game2048` a
variable, long horizon + board state + CNN extractor + action masking. If the
current workspace contains other spec-conformant domains, prefer whichever
matches the problem shape (multi-entity, discrete + action masking,
deterministic dynamics + scenario-sampled instances, …); otherwise
generalize from the shipped two plus the spec's patterns.

### Stage 1 — model layers

Write in dependency order: `{domain}_exceptions.py` (optional) →
`{domain}_uncertainty.py` (omit if dynamics deterministic) →
`{domain}_scenarios.py` → `{domain}_mdp.py`.

- Seed keys follow the **§6.3 v2 seed tree**: declare `SEED_SCHEME = "v2"`
  and set `seed_scheme: "v2"` in the IR; build every key through the
  domain's `intrinsic_key()` / `meta_key()` helpers (never a raw
  `SeedSequence`). The interpreter implements both schemes and the
  differential gate is bit-exact under v2 (`examples/inv_single` is the
  reference); `"v1"` exists only for pre-redesign domains with recorded
  results.
- A generator whose distribution depends on the current decision takes it
  as an extra `sample(ctx, <decision>)` argument; the seed key must still
  contain only `(period, episode_seed, seed_salt)` slots so realized
  randomness is decision-path independent.
- Write a differential adapter **in the domain folder**:
  `{name}/{ir_name}_ir_adapter.py` exposing
  `make_adapter(ir, instance=None, seed_salt=0, domain_dir=None)` with
  `domain_dir` defaulting to the adapter's own directory
  (`mdp_ir.differential.load_adapter_factory` discovers it from the IR
  file's directory; `examples/inv_single/inv_single_ir_adapter.py` is the
  reference). Build the scenario **from the IR's constants**; emit
  row keys named exactly like interpreter rows; pass `seed_salt` through.
- Give `{domain}_mdp.py` a `__main__` smoke episode and run it.

- Write `{domain}_test.py` in the domain folder (spec §1.2): the engine laws,
  the differential parametrized over the covering set derived from the schema,
  at least one negative control, and any claim an IR expression cannot state.

- Emit the folder's two campaign docs from the templates beside this skill,
  in the shapes spec §1.3 fixes. They divide by direction: `CLAUDE.md` points
  **up**, at the specs governing the folder; `README.md` points **across**, at
  everything the campaign produces. A campaign document is inventoried in
  exactly one of them.
  - `{domain}/CLAUDE.md` from `DOMAIN_CLAUDE_TEMPLATE.md` — five sections:
    fill the problem statement, the leaderboard-commensurability line, the
    three provenance lines (built-at, maintained-through, and *solved with* —
    the host, its version and the model driving this campaign) and the
    gate-command slots; keep the fixed text verbatim. The template's filling rules are binding — a pointer may name a
    destination, never describe or score what is inside it, and a hard rule is
    phrased trigger → destination.
  - `{domain}/README.md` from `DOMAIN_README_TEMPLATE.md` — the lead, the
    TL;DR block and four sections: write the one- or two-sentence lead (what
    the case is, its source, generated from the IR) and fill **The problem**
    and the two layout tables now (documents the campaign owes are listed
    "owed", never omitted); the TL;DR keeps its one-line placeholder, and the
    results and appendix sections are seeded as headings and grow as findings
    land, not at Stage 6.

- Emit `{domain}/.gitignore` beside it — three lines: `results/`, `scratch/`,
  `__pycache__/`. The folder must carry its own ignore rules (portable-domain
  contract): a contributed or promoted folder cannot assume the destination
  repo's root `.gitignore` covers them.

**GATE:** all three must exit 0 —
`python -m mdp_conformance {domain}` (generated-code shape),
`python -m mdp_ir.laws {domain}` (IR execution semantics), and
`python -m mdp_ir.differential {name}/{name}_schema.json --all-instances --episodes 40`.
The differential must be MATCH (bit-exact) with **no invariant violation**.
Then run `pytest {domain}` for the domain's own tests, and `pytest` at the repo
root to prove no regression to the shipped examples.

**Why bit-exact, and not just "tests pass":** formalization has no oracle —
dynamics can be modeled plausibly but wrongly, and a generated domain that
is self-consistent will pass every unit test while encoding the wrong
problem. The IR interpreter is the only independent implementation of those
dynamics, so replaying identical `(instance, episode_seed)` through both and
demanding an exact trajectory match is what actually catches a
mis-formalization. This is the reason the differential gate exists; treat a
MATCH failure as a modeling error to diagnose, never as a tolerance to relax.

### Stage 2 — gym wrapper

`{domain}_gym.py` from the IR's `gym` block: every observation mode (never
exposing latent state), every action mode with its declared strategy
(mask → `valid_actions` in the MDP layer + `action_masks()` on the env;
clip / reparametrize per spec §7.2), reward modes from objective
components in `info`, `terminated` at horizon + any early-termination expr.

- **The rendered vector is the declared vector** (spec §7): the modes the
  wrapper accepts are the modes the IR names, and each renders exactly that
  mode's `features`, in order. When the wrapper wants a component the IR does
  not declare, edit the IR first — `gym` is a mutable block, so a feature line
  costs no re-confirmation and moves no fingerprint. **The time feature is the
  one this loses**, in the domains that have one: where the horizon is the
  problem's own boundary (`termination.horizon_end: "terminated"`) the gym
  prepends one as a formatting decision, so it is written in the code and
  nowhere else unless you declare it. Where the horizon is only a cap
  (`"truncated"`) the problem is stationary, the policy must not see the clock,
  and there is nothing to declare — spec §7. No check catches either half; read the vector against
  the list by hand at the gate below — by *provenance*, not by width, since
  a mode may re-encode what it declares (a one-hot expansion of a declared
  board is compliant; an undeclared scalar prepended to it is not, and the
  two look alike if you only count). Spec §7 also states which encoding to
  prefer (`time_to_go = T - t`) and the horizon-proportional exception.

- **Action-box design lesson:** SB3 PPO's Gaussian initializes at raw
  action 0. If raw 0 maps to a dead zone (e.g. a shut-off intensity),
  learning stalls. Prefer encodings where raw 0 is a live, low-value
  action; keep the paper-native encoding as a non-default mode.

**GATE:** the module `__main__` runs random-action episodes in every
obs/action mode with `observation_space.contains(obs)` asserted each step,
and `python -m mdp_conformance {domain}` still passes.

Exit: both stage gates green. `<python> -m mdp_stage {domain} --for solve`
runs the same three checks as its entry — hand off to `mdp-solve` (or back to
the conductor).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.