kiln-batch-dispatch · git:20260905.2d832ea · 2026-09-05 · sha256 c478a152ee042967

kiln-batch-dispatch git:20260905.2d832eaA

Immutable. This exact content never changes and is served at /api/v1/blob/c478a152ee042967.

---
name: kiln-batch-dispatch
description: Wire the Kiln plugin into a coding agent CLI, prove the tools are actually reachable, and then dispatch many asset builds across harnesses and models in clean-room sandboxes. Use when asked to set Kiln up in a harness, to generate a batch or library of assets, to compare models on asset authoring, or when a dispatched run produced nothing and you need to know whether the wiring or the model was at fault.
license: MIT
---

# Set Kiln up, then run it at scale

Two jobs live here because they fail as one. Batch generation on unproven wiring produces a pile of
empty sandboxes and no way to tell whether the models were weak or the tools were never there. So
the order is fixed: wire it, prove it, then dispatch.

Everything below runs from a clone of this repository. `node scripts/...` paths are relative to its
root.

## 1. Wire the harness

`docs/install.md` is the reference, one section per harness, and it is the thing to read when a
command below does not match what the CLI in front of you accepts. The shape is the same everywhere:
point the harness at `dist/mcp-server.mjs` over stdio, and give it the `skills/` directory.

```bash
bun install && bun run build:mcp
```

`dist/mcp-server.mjs` is committed, so a fresh clone is already wired; rebuild after editing `src/`.

Claude Code and Antigravity read `.claude/skills` out of the working directory and take the plugin
whole. OpenCode and Codex take the MCP server through their own config file. Hermes is the one that
is genuinely different, a Python agent with its own YAML config and its own skill store, and it
needs `hermes config set skills.external_dirs` pointed at an absolute path, because it does not look
in the working directory at all.

Ask which harness the user actually has rather than assuming. If they have several, wire the ones
they name and leave the rest alone.

## 2. Prove the tools are reachable

```bash
bun run smoke:harness
```

Do not skip this, and do not accept an agent's own report in its place. Every wiring failure in this
system is silent, and they are mutually indistinguishable from the outside: a harness that cannot see
the server answers exactly like one that sees it and will not grant it, which answers exactly like a
model that was not up to the job. The smoke sends a four-line brief into a throwaway directory and
then builds the result with the engine, because the child's claim of success is the one piece of
evidence that proves nothing.

```
agy       ok    105s  gemini-3.8-flash-high  built 12 tris
claude    ok     13s  sonnet                 built 12 tris
hermes    ok     97s  (configured default)   built 12 tris
opencode  ok     37s  opencode-go/glm-5.3    built 12 tris
```

`--harness <name>` checks one. A failure prints the tail of the agent's output and the sandbox path,
which is left on disk: read the brief it was given and whatever it wrote before deciding what broke.
A harness that is not installed is skipped, not failed.

Two failures look alike and are not:

- **No program written.** Either the tools never arrived, or the provider refused the request. The
  printed output says which; a quota message is a quota message.
- **A program written that the engine rejects.** The tools arrived. This is a model result, and it is
  data worth keeping rather than a bug to fix.

## 3. Dispatch one asset

```bash
node scripts/dispatch-asset.mjs --harness opencode --model opencode-go/glm-5.3 --category architecture --timeout 30m --name covered-bridge "a New England covered bridge: a single timber span on stone abutments, a Town lattice truss of crossed planks pegged at every intersection, board-and-batten cladding, a shingled gable roof"
```

What it does, and why each part is load-bearing:

- **A clean room.** Every run gets a fresh temp directory containing the brief and the Kiln skills,
  and nothing else. No repository, no finished examples, no other assets to copy from. This is the
  whole evidentiary value of a dispatch. An agent that can see `examples/` is being tested on its
  ability to read, and the result says nothing about whether the tools teach a model to build.
- **Absolute paths in the brief.** Every CLI resolves relative paths against something different, and
  at least one resolves them against its own installed plugin copy rather than your working tree. A
  brief that says `out/thing.js` writes to a file you will never find.
- **A category, and `prop` is a real answer.** `--category` takes one of `prop`, `character`, `vfx`,
  `environment`, `architecture`, `vegetation`, `vehicle`, defaults to `prop`, and rejects anything
  else. It sets `meta.category` in the program, and for the six non-default values it appends a short
  brief about what tends to go wrong for that kind of subject.

  Those six are **experimental**. Almost anything can be built as a prop, the author skill already
  covers how, and the empty `prop` brief is the least constrained thing this script can send. Naming
  another category trades that freedom for a hint, which pays when the subject really is one of those
  things and costs when it is not -- pushing storey rhythm and eaves at a bandstand, or branching
  rules at something that is a plant in name only, makes the result worse than saying nothing. Reach
  for one deliberately, and if a run comes back worse than the same brief as a prop, drop the category
  before you blame the model.

  What the flag definitely buys, whichever value you pass, is that `meta.category` stops being a
  constant. Before it existed the brief hardcoded `'prop'`, so the field carried no information at
  all and a library could not be sorted by it.
- **The subject, written like a brief and not like a title.** Name the parts. "a trebuchet" gets a
  beam on a frame; naming the sling, the pawl and the iron straps at the joints gets those things.
  The prompts that produced the best assets in this repository read like a description written for
  someone who has to build the thing.
- **`--animate`** when the motion is the subject. A locomotive without moving rods is a shed on
  wheels. It adds the rig requirements to the brief and makes the child prove the loop.

The dispatcher renders whatever came back, writes a `.result.json` beside it with the model, harness
and triangle count, and stamps the authoring model into the file's header. When a run is cut off by a
deadline or a provider limit it keeps the program that was on disk and records that it was
interrupted, because a cut-off run still produced something worth looking at.

## 4. Dispatch a batch

There is no batch subcommand and there does not need to be one. A shell loop over the same script is
the whole feature, and it keeps each run independent so one failure does not take the rest with it.

```bash
for spec in "covered-bridge|architecture|a New England covered bridge with a Town lattice truss" "banyan-tree|vegetation|an old banyan tree with aerial prop roots fused into secondary trunks" "monorail-car|vehicle|a straddle-beam monorail car gripping a concrete guideway"; do
  IFS='|' read -r name category subject <<<"$spec"
  node scripts/dispatch-asset.mjs --harness opencode --model "$MODEL" --category "$category" --timeout 30m --name "$name" "$subject"
done
```

Run several such loops in parallel rather than one long one. They are separate processes against
separate sandboxes. Keep each loop sequential so its runs do not contend for the same model's rate
limit.

Choosing what to spread across:

- **Across models, same subject** answers "which model builds this better". Keep the brief identical
  or the comparison means nothing.
- **Across subjects, same model** builds a library. Spread the *subjects* widely -- a batch of ten
  variations on a crate tells you less than ten different things -- and note that spreading subjects
  is not the same as spreading categories. Ten wildly different props is a good batch; relabelling
  three of them to get a fuller category list is not.
- **Across harnesses** tests the tools rather than the models, and is worth doing once after any
  change to the server or the skills.

Two practical limits, both learned the hard way:

- **Output token caps.** Some harnesses default to a cap far below what the provider allows, and a
  model that hits it stops mid-file and leaves nothing on disk. `scripts/harness.mjs` reads the
  declared limit per model and raises it per run; if you add a harness, do the same there rather than
  working around it one dispatch at a time.
- **Timeouts are budget, not correctness.** A thirty-minute run cut off at minute thirty with a
  usable program on disk is a success. Read `interrupted` in the manifest before calling one a
  failure.

## 5. Judge, then keep

Render everything the same way before comparing anything, because differences in framing and lighting
will dominate your judgement of the assets themselves:

```bash
node scripts/hero-shots.ts
```

Look at them. A batch is not a result until someone has looked at every image in it. What separates a
keeper from a near miss is almost never the triangle count. It is whether the parts meet, whether the
thing stands on the ground, and whether a saturated albedo has blown out to flat white under the
studio dome.

```bash
node scripts/promote-asset.mjs <name>
```

Promotion copies the program into `examples/`, re-runs it to get the triangle count rather than
trusting the dispatch log, and writes the provenance header from the manifest, including whether the
run actually had a clean room. If you repair an asset by hand afterwards, say so in that header. The
gallery makes a claim about who wrote each program, and a claim you have quietly edited is worse than
no claim at all.