# Kiln Engine Agent Guide

## Scope

This repository turns model-authored source into GLBs: deterministic rendering, QA,
primitives, agent tools, arena ranking, scene composition, CLI and MCP + skills.

Read [README.md](./README.md) before changing exports or package contents. Runtime and tests
live under `src/`; repository checks under `scripts/`. `package.json` lists shipped files
through `files` and `exports`. [CHANGELOG.md](./CHANGELOG.md) states what each
release changed. Dated files under `docs/plans/` and `docs/reviews/` are records of their
day: they never override current code, this guide or the changelog, and never imply release
acceptance.

## Authoring an asset is a different task from changing the engine

This guide covers engine changes. Author assets in a separate workspace with a live
`kiln_workspace` server: exposing engine source and examples changes model outputs.
Before asset authoring, read `kiln-setup-workspace` and create a workspace. Its maintained
copy is `skills/kiln-setup-workspace/`, registered at `.claude/skills/` and `.agents/skills/`.

Feed demonstrated authoring defects back into the maintained skills at the relevant
boundary: shared shape and fit guidance in
`skills/kiln-author-asset/references/geometry-recipes.md`; destination traversal,
interaction and performance evidence in the QA integration reference. Keep scene-specific
controllers and asset dimensions in authored workspaces. Interactive proving environments
use the optional `skills/kiln-compose-scene/` workflow and its runtime-scenes reference;
scene terrain, controllers and optimization derivatives never become requirements for
standalone assets. Guidance changes do not retroactively qualify an existing asset or
replace its owner review. Generated workspaces copy these skills; verify the copies and
package contents without silently rewriting existing authors' installed instructions.

## Optional projects

Standalone assets, materials and review must work without projects. Do not infer membership
from nearby projects or impose pack-specific defaults. Read the
[selection and delivery contracts](docs/projects-and-live-review.md): configured
`KILN_PROJECT` is opt-in; `--no-project` / `projectId: null` overrides it. The collection
named `project` is only a destination. Keep `skills/`, registered setup copies and the
generated `AGENTS.md`/`CLAUDE.md` in `scripts/create-workspace.mjs` aligned; qualify fresh
setup and managed upgrade without overwriting user customizations.

## Library and gallery assets

The Workshop Library aggregates configured asset collections; collection `project` is
workspace storage, not project membership. Keep collection-qualified IDs through browsing,
revision selection and project links. Projects pin exact saved revisions. Live Review's
optional host `KILN_WORK_ITEM` groups one authoring item across CLI/MCP sessions; never
infer it from project, model name or source equality. Explicit observation workspaces stay
separate sources. Build and check viewer changes through the installed package and the
actual Library/Projects/Live Review flow, not only a separate comparison page.

Gallery assets are unvetted historical showcases, not golden outputs; regression comparisons
do not establish their quality.

## The tool registry is the single source of truth

`src/tools/registry.ts` owns tool names, descriptions and schemas; `src/tools/capture-input.ts`
states the camera shapes once and `src/tools/actions.ts` checks what each flat `action` needs.
**Both Strands (`src/agent/tools.ts`) and stdio MCP must iterate it.** Never hand-write
definitions in a skin. Keep the name-parity test passing. `docs/tools.md` is the packaged
manifest, generated by `scripts/generate-tool-reference.ts`. Records advertised opaque
(`draft`, `patch`, `payload`, `capture`, `shot`) are served in full by Discovery `shape:`
entries and validated when the tool runs; `src/tools/__tests__/registry-contract.test.ts`
holds the size and shape budgets.

The stdio executable is a thin entry: `src/mcp-server.ts` bundles the protocol library and
`src/generated/mcp-manifest.json` (regenerated by the `mcp` build) and loads
`dist/mcp-engine.mjs` on the first call that needs the engine; handlers and cache hints are in
`src/mcp-core.ts`. Never import the engine statically from the entry or the core; an
engine-loading problem is a tool result naming the next step, never a dead server.

`createKilnProgramToolRegistry` supplies the fourteen base MCP tools. The packaged local host
also injects `kiln_project`, `kiln_material` and `kiln_review` (seventeen); embeddings
advertise these only when the host services exist, never as tools that always fail.
`createKilnNativeToolRegistry` uses the same definitions and adds `kiln_finish`: ten tools
by default, fifteen with an injected `assetLibrary` (the five delivery tools). A configured
skill-resource reader adds `kiln_skill_resource` to either native set.

Both skins use unified `kiln_render`: metrics, part paths, QA, images and fidelity share one
evaluation. `kiln_validate` is the image-free syntax check. Review results are compact by
default and `detail: 'full'` returns the complete QA report; compaction happens at the
output boundary, after the retained artifact is recorded (`src/tools/review-detail.ts`).

Native completion uses `kiln_finish({programRef})`, defined in the registry. It selects an
exact retained reviewed artifact without re-evaluation; completion and QA acceptance stay
separate. A completion call runs alone; immutable edits and reads may run concurrently. The
MCP surface has no terminal action because its host owns the outer loop and delivery.

Strands harness guidance is registered by `src/agent/native-workflow.ts`, outside the shared
`skills/` tree. Keep its completion/recovery protocol out of CLI/MCP setup, generated
workspace instructions, Discovery and shared skills. See
[the native workflow boundary](docs/runtime.md#optional-native-strands-workflow).

## Host-injected render and cache boundaries

Kiln Discovery is `kiln_discover` / `kiln discover`; helper specs and the typed catalog live
under `src/discovery/`, and `./discovery` is the public entrypoint. Search is ranked local
text retrieval with reviewed metadata: no models, network, provider keys or GPU, and no
semantic enhancement without an explicit adoption decision.

The engine defines the host-injected `PbrRenderPort`. Exported `captureViewsViaPort` is the
**single owner** of the deadline, renderer/PNG validation, grid composition and never-throw
CPU fallback. Do not duplicate that degrade policy in a host or add network/service knowledge
to deterministic engine paths. Keep the two injection deadlines separate: the post-loop
artifact sheet can wait; `KilnToolContext.viewRenderPort` serves the blocking in-loop
`kiln_render` grid with a much lower per-call deadline.

Route by `sceneNeedsPbrShading(root)`: bound texture or `metalness > 0`, never material type
(`gameMaterial` and `pbrMaterial` both make `MeshStandardMaterial`). Host telemetry uses
`onViewsRendered`; every `kiln_render` returns model-visible `ViewFidelityV1`. CPU geometry
images are not material evidence. Tool definitions are prompt-cached: keep input schemas
stable unless a change explicitly versions them.

`review-neutral-v1` is the default review rig: a calibrated white rig with Review Neutral
tone mapping, so a matte surface reads back near its authored albedo. Explicit lighting IDs
stay in capture cache keys. `src/views/background.ts` owns the named backdrops;
`render-service/src/backdrops.mjs` mirrors them, guarded by `backdrop.test.ts`, and
`render-service/src/display-transform.mjs` makes GPU and CPU backdrop pixels match. Captures
accept only `neutral` (default), `dark` or `light`, never free colours. Both capture-cache
keys include the id; results echo `capture.backdrop`; `kiln_save` records
`preview.backdrop`.

**One render service per machine, and the socket is its registry.** Sessions share port 8000
(or `KILN_RENDER_SERVICE_PORT`) and join only a validated compatible protocol, instance,
build, dependency and capture identity. A timed-out listener is unknown, not a verified busy
renderer. Stale, incompatible and foreign listeners are reported without replacement;
`kiln service stop` is the explicit action. Managed services have a bounded idle lifetime
(five minutes by default) protected by all admitted work; host exit must not kill a shared
service or another client's work. Manual services stay long-lived. Service and host share
`render-service/src/build-identity.mjs`; exact resolved dependency pins count. Do not add
lease files or another discovery path. In-session `kiln_renderer` reprobe refreshes
availability for the existing route and does not install dependencies.

Render requests are self-contained GLBs. Admission, upload time, decoded PNG allocations,
geometry and scene depth are bounded before loading. Cancellation removes queued work.
Propagate the optional second-argument execution signal through every `PbrRenderPort`
wrapper, including capture caches, while deadline and CPU degradation stay in
`captureViewsViaPort`.

**The GPU is a view producer only, never gate evidence.** `QaContext` is deliberately
image-free so a QA rule structurally cannot read a render buffer. Do not add pixels to it.

`src/views/renderer-id.ts` calls `readFileSync` at MODULE LOAD. Reach
`CPU_RASTER_RENDERER_ID` through existing lazy `await import('../views')` paths; static
imports add a Node-only filesystem edge that no test catches.

Prompt-cache transports differ deliberately. Native Anthropic and Bedrock adapters consume a
system `[TextBlock, CachePointBlock]`; OpenRouter-hosted Anthropic keeps plain system text
and receives top-level `cache_control: { type: 'ephemeral' }` because its Vercel bridge drops
cache-point blocks. Preserve that and the provider usage fields when changing model routing.

## Toolchain and validation

Maintainer toolchain: Bun `1.4.2`; Node `22.23.2`; npm `12.0.2`, pinned in `toolchain.json`.
Consumer Node compatibility is defined separately in `src/runtime-support.mjs` and
`engines.node`; do not add Bun or npm maintainer pins to end-user `engines` or use a Bun
canary or implicit latest for a release gate.

```bash
bun install --frozen-lockfile
bun run check:toolchain
bun run check:skills
bun run typecheck
bun run lint
bun run test
bun run test:render-service
bun run test:coverage
```

`bun run build` is a typecheck only and refreshes neither `dist/` nor its build identity. After
runtime source changes, rebuild the Node bundles with `node scripts/build-runtime.mjs all`
(`bun run build:runtime` when viewer assets also changed) before CLI/MCP dogfood and full gates.

Fast loop: nearest test file first, then `bun run typecheck && bun run lint && bun run test`.
The full offline gate adds `bun run test:coverage`, which emits `coverage/lcov.info` and
enforces the line/function ratchet in `bunfig.toml`. Raise thresholds when practical; never
lower them without a measured rationale. Live model tests are opt-in via `bun run test:live`.

`bun run test` is `bun test src scripts` and does **not** reach `render-service/`; run
`bun run test:render-service` whenever you change it (CI requires it).

Tests use `--timeout 20000` for cold Windows startup; a larger budget needs an explicit third
`test()` argument with a measured-duration comment. Tests and CI pin `KILN_RENDER=cpu` so the
coverage ratchet cannot vary with the runner's GPU; coverage is measured over `src/` alone, and
tests under `scripts/` still run.

Use strict test-driven development for behavior changes: add a focused failing test, observe
the expected failure, implement the smallest fix, and rerun both the focused and full gates.
Preserve determinism: no `Date.now()` or `Math.random()` in render/rasterizer compute paths.

## Safety

- Never print, read unnecessarily, or commit provider keys, cloud credentials, `.env*`, or
  secrets.
- Keep normal validation offline. Do not invoke live providers unless explicitly requested.
- Do not commit, push, force-push, create remotes/releases, or publish packages without
  explicit user approval.
- Live model and arena runs spend money. Keep them manual and out of CI.
