Skip to content

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 with NaN refs), 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/… export cashNlheFrame / 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 validateUserRegion inside the locked transaction — an undefined or 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_current is a coded 409 (the UI's apply button is absent when current — affordance gate). Both maps enter versionHash when 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); /seat refuses prompt_pin_unresolved (fail-closed), reconnects degrade to legacy LOUDLY. The resolved composition rides AgentDeployment.seatComposition and a separatebuildTurnContext argument — deliberately not turnOpts, 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).