Files
PIG-Demo/CONTRACT.md
T
karti-ai 2c2dcad9fd Document the contract and the traps
CONTRACT.md states the twelve rules and, for each, the failure it prevents.
AGENTS.md lists the three directories a demo agent may touch and the seven traps
that have already cost time here — pnpm 11's settings move, the non-portable
RNG, the two-pass rule, the two reward-denominator subtleties, the silent CSP
worker block, and Caddy's silent bind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 15:48:20 -07:00

105 lines
4.4 KiB
Markdown

# The demo contract
Every demo is one directory under `src/demos/<slug>/` plus one Python package
under `envs/`. The shell renders anything that satisfies the interfaces in
`src/lib/demo-kit/types.ts`. Nothing else in the repository needs editing —
the header, the gallery, the router and the sitemap are all generated from the
registry, and the registry finds demos by existence.
That is the whole design goal: **demo number nine must not be able to break
demo number one.**
## The rules, and why each exists
1. **The directory name is the slug.** Two sources of truth for a URL is one
too many.
2. **`meta.ts` is eager, serialisable and React-free.** It is imported for
every demo on every page load, because the header needs all of them to
render. `icon` is a lucide icon *name*, not a component — importing the
component would pull lucide into the entry chunk on behalf of a demo nobody
opened.
3. **`demo.tsx` is lazy.** Everything expensive lives behind it.
4. **A demo imports from `@/lib/demo-kit` and nowhere deeper.** Reaching into
`@/lib/demo-kit/player` couples a demo to an implementation detail; the
barrel is the contract's surface.
5. **A demo never imports from `@/components/demo/`.** The shell renders demos;
demos do not reach into the shell.
6. **The shell never mentions a slug.** `if (slug === 'wordle')` in
`src/components/demo/` is a contract bug — either fix the contract or expose
a `SlotRegion`. `check-demos` fails on it.
7. **Reward weights sum to 1.0, and at least one component is a
`counterweight`.** A reward with only objectives teaches the crude version of
what you asked for.
8. **A counterweight must be in genuine tension with the objective.** If every
good policy also scores 1.0 on it, it is a `gate` — declare it as one. The
probe ladder is where you prove the difference: two good policies, neither
dominating.
9. **`null` is "not scored", never `0.0`.** A zero is a claim that the policy
did badly. A null is an admission that we do not know. Rendering the second
as the first is the quiet way a demo starts lying.
10. **Every `DemoStep` sets `announce`.** `prefers-reduced-motion` clamps every
animation to nothing, so colour alone carries the result — and colour alone
is not a result. The announcement *is* the feedback for anyone not looking
at the screen.
11. **A `RunRef` with `kind: 'intervened'` must name its `intervention`.** This
exists so a prompt change can never be presented as a training result by
omission.
12. **A `spec` demo ships a real specification** — task, legal actions,
deterministic grader, counterweight, and the eval command that would run
it. A coming-soon card is not a specification, and `check-demos` refuses one.
## Adding a demo
```bash
pnpm demo:new claims # copies src/demos/_template and envs/_template
```
Then, in order:
1. **Write the environment first**, in `envs/<pkg>/`. The engine, the reward,
and a probe ladder. If the reward cannot be probed, it cannot be trusted, and
nothing downstream is worth building.
2. **Port the scorer to TypeScript** and add it to the conformance gate. The
browser must be able to re-derive what the environment recorded, or the
verify badge is decoration.
3. **Capture rollouts** with `envs/capture.py`. Include at least one run the
agent loses; a demo where the agent always wins teaches nothing about the
reward.
4. **Write `meta.ts`, `narrative.ts`, `surface.tsx`, `adapter.ts`** and the
demo module. The narrative's `limits` must name which demo answers each gap.
5. `pnpm check && pnpm build`.
## The marker syntax for code receipts
`RewardSpec.source.marker` selects a region of a source file to highlight:
```python
# region: pig-demo/reward
...
# endregion: pig-demo/reward
```
Exclusive of the marker lines, **exactly one pair per file**. Zero or two or
more is a fatal error in `check-receipts.mjs`, because a silently-wrong region
would quote the wrong code under a claim that it is the code that ran.
## What to expect to change
The contract was written against a turn-based game. The first vertical demo is
deliberately a one-shot classifier, so it stresses the weakest axis — no engine,
no interactive driver, possibly a single run. **Budget one demo-kit refactor
there and treat it as expected rather than as a failure.** If more than a slot
is needed, widen the contract once, deliberately, updating `CONTRACT.md` and
`_template` in the same commit.