Appearance
Prompt Composition
The Prompt an agent reads is a typed composition of blocks under a platform-owned frame, resolved by the pure @numero/prompt-engine package. Owners build their strategy from named ; the platform ships universal content (the rulebook) through a runtime-editable registry; every published version freezes exactly what it renders.
The pipeline
- The engine is pure (
packages/prompt-engine): no db, no http, no react. Block kinds:universal(registry content, pinned by rev),user-region(the owner's strategy template withNaNrefs),skill-sections,runtime(game state / hand history — never frozen),literal(frame separators). Substitution is single-pass; values are never re-scanned; unknown refs stay literal. - Frames belong to skills (
apps/hosted-agent-service/src/skills/…exportcashNlheFrame/sngFrame). The frame is the platform's shape; the owner controls content, never order. - Legacy byte parity (T13): a composition-less version (both maps empty) renders through
renderLegacy— proven byte-identical to the historical join by the parity fixture (packages/prompt-engine/fixtures/legacy-parity.json). Regenerating that fixture is a contract change, not housekeeping.
The registry
prompt_variables + prompt_variable_revisions (migrations 0099), env-scoped like claim_sections ('all' default, concrete-env override). Revisions are append-only — no UPDATE path exists anywhere; editing means inserting rev N+1. Operator surface: game-server /admin/prompt-variables* (x-admin-key, admin.prompt_vars.* spans). Owners READ universal content through the BFF (POST /hosted-agent/resolve-universals, JWT) — it renders inside their own prompt.
Seed: apps/hosted-agent-service/scripts/seed-prompt-variables.ts writes each poker skill's basePrompt VERBATIM as rev 1 (poker-cash-nlhe-base, poker-sng-base) — the byte-faithful migration.
The pin + upgrade lifecycle
- A fresh draft snapshots the registry's latest revisions as its pins; forks inherit their source's pins verbatim (upgrade is prompted, never silent).
- Publish runs
validateUserRegioninside the locked transaction — an undefinedor illegal var name is a coded 409 (prompt_ref_undefined/prompt_var_name_invalid), the tx rolls back, the draft stays editable. Unused variable definitions never block (a library you're building is fine). - The upgrade (
upgradePins) mints a pins-only-diff version under the same per-agent advisory lock + off-every-table gate as publish/revert.pins_currentis a coded 409 (the UI's apply button is absent when current — affordance gate). Both maps enterversionHashwhen non-empty; empty maps canonicalize identically to the legacy 5-field shape, so pre-composition hashes stay recomputable forever.
Runtime + capture
- Seat/reconnect/advisor paths resolve pins ONCE (
resolveVersionComposition);/seatrefusesprompt_pin_unresolved(fail-closed), reconnects degrade to legacy LOUDLY. The resolved composition ridesAgentDeployment.seatCompositionand a separatebuildTurnContextargument — deliberately notturnOpts, which is spread wholesale into the capture's knowledge record and would duplicate multi-KB pinned content per turn. - The capture record carries
promptProvenance(kind/key/rev per envelope section — ids only; migration 0101, nullable →undefined, never[]). Replay-a-decision accepts a draft's composition and renders through the same engine paths.
The Brain UI
/agents Brain mode is the two-view composer (apps/poker/app/agents/_components/brain/): Basic (rulebook readable in place, plain game-plan textarea, parts, brain) ⇄ Fine-tune (the frame stack — universal blocks with rev/advance chips, your region with variable chips, the variable library). Both views consume ONE useComposition state; the view choice rides the URL (view param). Grammar (extractRefs / isValidVarName) is imported from the engine — no view re-implements it.
Tests
- Engine:
packages/prompt-engine/src/__tests__/(15) + the T13 parity anchor on both halves. - Integration (
test/prompt-composition/): publish validation (T5/T6), registry append-only (T14), runtime render against the live registry (T4/T8/T15), the upgrade loop (T10/T11/T16). - UI:
apps/poker/app/agents/__tests__/lab-composer.test.tsx(T1/T2/T3/T7/T9).