@warlock.js/ai 4.6.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/cjs/index.cjs +3 -1
- package/cjs/{src-Bmajk4Qg.cjs → src-DBn2_pbG.cjs} +1 -1
- package/cjs/{src-OZyDYHxm.cjs → src-DTlN47aO.cjs} +552 -17
- package/cjs/src-DTlN47aO.cjs.map +1 -0
- package/esm/agent/agent-input-builder.mjs +1 -0
- package/esm/agent/agent-input-builder.mjs.map +1 -1
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/system-prompt.contract.d.mts +148 -1
- package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
- package/esm/errors/error-code.type.d.mts +1 -1
- package/esm/errors/index.d.mts +1 -0
- package/esm/errors/index.mjs +1 -0
- package/esm/errors/prompt-refinement-error.d.mts +36 -0
- package/esm/errors/prompt-refinement-error.d.mts.map +1 -0
- package/esm/errors/prompt-refinement-error.mjs +27 -0
- package/esm/errors/prompt-refinement-error.mjs.map +1 -0
- package/esm/index.d.mts +4 -2
- package/esm/index.mjs +3 -1
- package/esm/prompts/prompts-manager.d.mts.map +1 -1
- package/esm/prompts/prompts-manager.mjs +1 -1
- package/esm/prompts/prompts-manager.mjs.map +1 -1
- package/esm/prompts/prompts-manager.type.d.mts +15 -0
- package/esm/prompts/prompts-manager.type.d.mts.map +1 -1
- package/esm/prompts/prompts-validate.mjs +0 -0
- package/esm/prompts/prompts-validate.mjs.map +1 -1
- package/esm/system-prompt/index.d.mts +1 -0
- package/esm/system-prompt/index.mjs +1 -0
- package/esm/system-prompt/refined-system-prompt.d.mts +184 -0
- package/esm/system-prompt/refined-system-prompt.d.mts.map +1 -0
- package/esm/system-prompt/refined-system-prompt.mjs +461 -0
- package/esm/system-prompt/refined-system-prompt.mjs.map +1 -0
- package/esm/system-prompt/system-prompt.d.mts +14 -1
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs +19 -0
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/llms-full.txt +104 -1
- package/llms.txt +2 -1
- package/package.json +3 -3
- package/skills/README.md +4 -0
- package/skills/manage-prompts/SKILL.md +8 -1
- package/skills/refine-prompts/SKILL.md +91 -0
- package/skills/write-system-prompt/SKILL.md +1 -0
- package/cjs/src-OZyDYHxm.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/system-prompt/system-prompt.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { Placeholders } from \"../contracts/placeholders.type\";\nimport type {\n InstructionContract,\n PersonaContract,\n SystemPromptBlockContract,\n SystemPromptContract,\n SystemPromptMergeOptions,\n SystemPromptMeta,\n} from \"../contracts/system-prompt.contract\";\nimport { InvalidRequestError } from \"../errors\";\nimport { defaultPromptsManager, promptKey } from \"../prompts/prompts-manager\";\nimport type {\n PromptValidationResult,\n PromptsValidateOptions,\n} from \"../prompts/prompts-manager.type\";\nimport { Instruction } from \"./instruction\";\nimport { Persona } from \"./persona\";\n\n/**\n * Monotonic source of the internal, non-registry display id every\n * `SystemPrompt` carries. Anonymous (unnamed) prompts have nothing else to\n * identify them by; this id never feeds the registry and is never derived from\n * the wall clock, so it stays stable and order-deterministic across a run.\n */\nlet displayIdCounter = 0;\n\n/**\n * Narrow an arbitrary value to a `SystemPromptContract` — true when it exposes\n * the builder surface (`blocks` array + a callable `resolve`). Used by the\n * registry-aware `merge` overload to tell a folded contract from a raw block\n * or a registry name string, robustly across duplicate package copies.\n */\nfunction isSystemPromptContract(\n value: unknown,\n): value is SystemPromptContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { blocks?: unknown }).blocks) &&\n typeof (value as { resolve?: unknown }).resolve === \"function\"\n );\n}\n\n/**\n * Build the deterministic provenance label for a prompt — `name@version` when\n * it is registered, otherwise its internal display id. No random suffixes, so\n * the same source always yields the same `composedFrom` entry.\n */\nfunction provenanceLabel(prompt: SystemPromptContract): string {\n const meta = prompt.meta();\n\n if (meta?.name) {\n return promptKey(meta.name, meta.version ?? \"1\");\n }\n\n return prompt instanceof SystemPrompt ? prompt.id : \"anonymous\";\n}\n\n/**\n * Concrete `SystemPromptContract` — an immutable layered prompt builder.\n *\n * **Role.** The top-level composer for a system prompt: it holds an ordered\n * list of typed blocks (persona + instructions) and resolves the whole\n * stack into one final string when the agent is about to call the model.\n *\n * **Responsibility.**\n * - Owns: the ordered `blocks` list and the block-join rules (insertion\n * order, blank-line separator, trim).\n * - Does NOT own: how any individual block is rendered (delegated to each\n * block's `resolve()`), the placeholder syntax (delegated to\n * `renderPlaceholders`), or any knowledge of the agent, model, or\n * session consuming the resolved text.\n *\n * Blocks are discriminated by a string `type` tag (`\"persona\"` /\n * `\"instruction\"`) rather than `instanceof`, so user-supplied blocks that\n * implement `SystemPromptBlockContract` interoperate seamlessly with blocks\n * built via `ai.persona()` / `ai.instruction()` — even across duplicate\n * package copies or bundler scope boundaries.\n *\n * The builder is **immutable** — every `.persona()` / `.instruction()`\n * call returns a fresh `SystemPrompt` instance sharing nothing mutable\n * with its parent. This makes forking a base prompt into specialized\n * variants a safe, side-effect-free operation.\n *\n * Users construct via the `ai.systemPrompt()` factory — `new SystemPrompt()`\n * is not the public API (see §4.2 of code-style.md). Modeled as a class so\n * that methods live on the prototype (one copy shared across every forked\n * instance) and downstream code can branch via `instanceof SystemPrompt`.\n *\n * @example\n * // Chainable form\n * const alex = ai.persona(\"You are Alex, a TypeScript expert.\");\n * const replyIn = ai.instruction(\"Respond in {{language|English}}.\");\n *\n * const base = ai.systemPrompt().persona(alex).instruction(replyIn);\n * const arabicVariant = base.instruction(\"Prefer Arabic comments.\");\n *\n * base.resolve({ language: \"English\" });\n * arabicVariant.resolve({ language: \"Arabic\" });\n *\n * @example\n * // Array form — insertion order is preserved exactly\n * const prompt = ai.systemPrompt([\n * ai.persona(\"You are Alex, a TypeScript expert.\"),\n * ai.instruction(\"Respond in {{language|English}}.\"),\n * ]);\n */\nexport class SystemPrompt implements SystemPromptContract {\n /**\n * Internal, non-registry id for display / provenance. Stable for the life of\n * the instance; sourced from a monotonic counter, never the wall clock.\n * Anonymous prompts are identified solely by this id.\n */\n public readonly id: string;\n\n public constructor(\n public readonly blocks: readonly SystemPromptBlockContract[] = [],\n private readonly metaData?: SystemPromptMeta,\n ) {\n this.id = `prompt#${displayIdCounter++}`;\n\n // Auto-register the moment a builder acquires a name — whether through the\n // `systemPrompt(input, { name })` factory or a `.meta({ name })` rename.\n // Forks built by `persona()` / `instruction()` / `merge()` deliberately\n // drop the name (they pass no meta), so they stay anonymous and never land\n // in the registry unless explicitly re-named.\n if (metaData?.name) {\n defaultPromptsManager().register(this);\n }\n }\n\n /**\n * Read the current metadata snapshot (no argument) or derive a renamed\n * builder (with `meta`). The accessor returns `undefined` for an anonymous\n * prompt; the updater shallow-merges `meta` onto the current metadata and\n * returns a fresh builder. Naming the result registers it in `ai.prompts`.\n */\n public meta(): SystemPromptMeta | undefined;\n public meta(meta: SystemPromptMeta): SystemPromptContract;\n public meta(\n meta?: SystemPromptMeta,\n ): SystemPromptMeta | undefined | SystemPromptContract {\n if (meta === undefined) {\n return this.metaData;\n }\n\n return new SystemPrompt(this.blocks, { ...this.metaData, ...meta });\n }\n\n /**\n * Build a system prompt by reading the file at `path` once, synchronously,\n * at construction time. The file's UTF-8 contents seed a single instruction\n * block — the same semantics as the string-seed form of `systemPrompt()` —\n * so placeholders inside the file (`{{language|English}}`) resolve at\n * `resolve()` time and the result can be forked with further\n * `.persona()` / `.instruction()` calls.\n *\n * One-shot by design: the file is read exactly once here, never re-read on\n * `resolve()`. Reads are synchronous so the call stays a drop-in for the\n * synchronous `systemPrompt()` factory and the synchronous `resolve()` API.\n *\n * Throws `InvalidRequestError` when the file cannot be read (missing path,\n * permission denied) — surfacing the underlying cause so a typo in the\n * prompt path fails loudly at construction instead of silently producing an\n * empty prompt.\n *\n * @param path - Filesystem path to the prompt template file.\n *\n * @example\n * const prompt = SystemPrompt.fromFile(\"./prompts/support-agent.md\");\n *\n * const localized = prompt.instruction(\"Respond in {{language|English}}.\");\n * localized.resolve({ language: \"Arabic\" });\n */\n public static fromFile(path: string): SystemPrompt {\n let contents: string;\n\n try {\n contents = readFileSync(path, \"utf8\");\n } catch (error) {\n throw new InvalidRequestError(\n `Failed to read system prompt file \"${path}\" — ${\n error instanceof Error ? error.message : String(error)\n }`,\n { context: { path }, cause: error },\n );\n }\n\n return new SystemPrompt([new Instruction(contents)]);\n }\n\n /**\n * Return a new builder with the persona block set. If a persona already\n * exists it's replaced in place (preserving its position in `blocks`);\n * otherwise the new persona is prepended so persona-first remains the\n * default for chain-built prompts. Accepts either raw text (auto-wrapped\n * via `new Persona`) or an existing `PersonaContract` instance for reuse\n * across prompts.\n */\n public persona(value: PersonaContract | string): SystemPromptContract {\n const block = typeof value === \"string\" ? new Persona(value) : value;\n const existingIndex = this.blocks.findIndex(\n candidate => candidate.type === \"persona\",\n );\n\n if (existingIndex >= 0) {\n const next = [...this.blocks];\n next[existingIndex] = block;\n\n return new SystemPrompt(next) as this;\n }\n\n return new SystemPrompt([block, ...this.blocks]);\n }\n\n /**\n * Return a new builder with the given instruction appended. Instructions\n * render in insertion order. Accepts either raw text (auto-wrapped via\n * `new Instruction`) or an existing `InstructionContract` instance for\n * cross-prompt reuse.\n */\n public instruction(\n value: InstructionContract | string,\n ): SystemPromptContract {\n const block = typeof value === \"string\" ? new Instruction(value) : value;\n\n return new SystemPrompt([...this.blocks, block]);\n }\n\n /**\n * Fold predefined blocks, another prompt contract, or a registered prompt\n * name into this builder. Three forms share one method:\n *\n * - `merge(...blocks)` — N pre-built `ai.persona()` / `ai.instruction()`\n * blocks. A `persona` block sets/replaces the single, leading persona;\n * every other block appends in order. `base.merge(reviewer, style, lang)`\n * equals `base.persona(reviewer).instruction(style).instruction(lang)`.\n * - `merge(contract)` — another prompt; its blocks fold in (persona\n * replaces, instructions append) and `meta.composedFrom` records the\n * provenance of both sides.\n * - `merge(name, { fromVersion })` — a prompt resolved from `ai.prompts`\n * (latest version unless `fromVersion` selects another); throws\n * `InvalidRequestError` when the name / version is unregistered.\n *\n * Immutable — the original builder is untouched; passing zero blocks returns\n * an equivalent builder. The folded result is anonymous (no `name`), so it\n * is never auto-registered even though it carries `composedFrom` provenance.\n */\n public merge(\n ...blocks: readonly SystemPromptBlockContract[]\n ): SystemPromptContract;\n public merge(source: SystemPromptContract): SystemPromptContract;\n public merge(\n name: string,\n options?: SystemPromptMergeOptions,\n ): SystemPromptContract;\n public merge(\n first?:\n | SystemPromptBlockContract\n | SystemPromptContract\n | string,\n // `undefined` is part of the element union so the `merge(name, options?)`\n // overload's optional trailing `options?` (i.e. `… | undefined`) stays\n // assignable to this implementation signature.\n ...rest: readonly (\n | SystemPromptBlockContract\n | SystemPromptMergeOptions\n | undefined\n )[]\n ): SystemPromptContract {\n // Registry-name form: resolve from ai.prompts at the chosen version.\n if (typeof first === \"string\") {\n const options = rest[0] as SystemPromptMergeOptions | undefined;\n const resolved = defaultPromptsManager().get(first, options?.fromVersion);\n\n return this.mergeContract(resolved);\n }\n\n // Contract form: fold another prompt's blocks + record provenance.\n if (isSystemPromptContract(first)) {\n return this.mergeContract(first);\n }\n\n // Variadic block form (the original behavior).\n const all = [\n ...(first ? [first] : []),\n ...rest,\n ] as readonly SystemPromptBlockContract[];\n\n return this.foldBlocks(this, all);\n }\n\n /**\n * Fold an ordered list of blocks onto a starting prompt: persona blocks\n * set/replace the single leading persona; every other block appends in\n * order. The shared core of the variadic-block `merge` and the contract fold.\n */\n private foldBlocks(\n start: SystemPromptContract,\n blocks: readonly SystemPromptBlockContract[],\n ): SystemPromptContract {\n return blocks.reduce<SystemPromptContract>((prompt, block) => {\n if (block.type === \"persona\") {\n return prompt.persona(block as PersonaContract);\n }\n\n return new SystemPrompt([...prompt.blocks, block]);\n }, start);\n }\n\n /**\n * Fold another prompt contract into this one (persona replaces, instructions\n * append) and stamp the deterministic `composedFrom` provenance — this\n * prompt's existing provenance (or its own label) followed by the folded\n * source's label. The result is anonymous so it never auto-registers.\n */\n private mergeContract(\n source: SystemPromptContract,\n ): SystemPromptContract {\n const folded = this.foldBlocks(this, source.blocks);\n\n const baseProvenance =\n this.metaData?.composedFrom ??\n (this.metaData?.name ? [provenanceLabel(this)] : []);\n\n const composedFrom = [...baseProvenance, provenanceLabel(source)];\n\n // Carry forward only provenance — never the name — so the merged result is\n // a fresh anonymous prompt (immutable rename = new key; original stays).\n return new SystemPrompt(folded.blocks, { composedFrom });\n }\n\n /**\n * Resolve every block against the placeholder map, join the results with\n * blank-line separators (in insertion order), and trim. Returns an empty\n * string when no blocks are present — callers treat that as \"no system\n * message\".\n */\n public resolve(placeholders?: Placeholders): string {\n return this.blocks\n .map(block => block.resolve(placeholders))\n .join(\"\\n\\n\")\n .trim();\n }\n\n /**\n * Validate this prompt via the process-wide `ai.prompts` manager — sugar for\n * `ai.prompts.validate(this, options)`. Runs the deterministic placeholder\n * check and, when `options.judge` is supplied, the Nova-safe LLM-as-judge\n * pass. Never throws on a judge failure; `ok` tracks the deterministic\n * verdict alone.\n */\n public validate(\n options?: PromptsValidateOptions,\n ): Promise<PromptValidationResult> {\n return defaultPromptsManager().validate(this, options);\n }\n}\n\n/**\n * Public factory for `SystemPrompt`, callable directly or via its\n * `fromFile` static. Exists as a named interface so the callable signature\n * and the `fromFile` attachment travel together as one public type.\n */\nexport interface SystemPromptFactory {\n (\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n ): SystemPrompt;\n\n /**\n * Build a system prompt from a file read once at construction. Delegates\n * to {@link SystemPrompt.fromFile}, so `ai.systemPrompt.fromFile(path)` and\n * `SystemPrompt.fromFile(path)` behave identically.\n *\n * @example\n * const prompt = ai.systemPrompt.fromFile(\"./prompts/support-agent.md\");\n */\n fromFile(path: string): SystemPrompt;\n}\n\nfunction systemPromptFactory(\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n): SystemPrompt {\n if (input === undefined) {\n return new SystemPrompt([], meta);\n }\n\n if (typeof input === \"string\") {\n return new SystemPrompt([new Instruction(input)], meta);\n }\n\n return new SystemPrompt([...input], meta);\n}\n\n/**\n * Create a new immutable system-prompt builder.\n *\n * **Role.** Public factory for `SystemPrompt` — keeps user-facing code\n * free of `new` and consistent with `ai.tool()`, `ai.agent()`,\n * `ai.persona()`, `ai.instruction()`.\n *\n * Input forms:\n * - No argument → empty builder, chain `.persona()` / `.instruction()`\n * - Single string → seeded with one instruction for quick one-shot prompts\n * - Array of blocks → used verbatim, preserving insertion order\n * - `.fromFile(path)` → seeded from a file read once at construction\n *\n * Pass a second `meta` argument to name the prompt — a named prompt\n * auto-registers in `ai.prompts` under `name@version` (version defaults to the\n * next integer). Forks (`.persona()`, `.instruction()`, `.merge()`) are\n * anonymous unless re-named via `.meta({ name })`.\n *\n * @example\n * // Composed builder\n * const prompt = systemPrompt()\n * .persona(\"You are Alex, a senior TypeScript engineer.\")\n * .instruction(\"Always include working code examples.\")\n * .instruction(\"Respond in {{language|English}}.\");\n *\n * prompt.resolve({ language: \"Arabic\" });\n *\n * @example\n * // One-shot seed\n * const prompt = systemPrompt(\"Answer only with JSON matching the schema.\");\n *\n * @example\n * // From a file, read once at construction\n * const prompt = systemPrompt.fromFile(\"./prompts/support-agent.md\");\n *\n * @example\n * // Array form — fully declarative\n * const prompt = systemPrompt([\n * ai.persona(\"You are Alex.\"),\n * ai.instruction(\"Always cite sources.\"),\n * ai.instruction(\"Respond in {{language|English}}.\"),\n * ]);\n */\nexport const systemPrompt: SystemPromptFactory = Object.assign(\n systemPromptFactory,\n { fromFile: SystemPrompt.fromFile },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"system-prompt.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/system-prompt/system-prompt.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { Placeholders } from \"../contracts/placeholders.type\";\nimport type {\n InstructionContract,\n PersonaContract,\n RefinedSystemPromptContract,\n RefinedSystemPromptOptions,\n SystemPromptBlockContract,\n SystemPromptContract,\n SystemPromptMergeOptions,\n SystemPromptMeta,\n} from \"../contracts/system-prompt.contract\";\nimport { InvalidRequestError } from \"../errors\";\nimport { defaultPromptsManager, promptKey } from \"../prompts/prompts-manager\";\nimport type {\n PromptValidationResult,\n PromptsValidateOptions,\n} from \"../prompts/prompts-manager.type\";\nimport { Instruction } from \"./instruction\";\nimport { Persona } from \"./persona\";\nimport { RefinedSystemPrompt } from \"./refined-system-prompt\";\n\n/**\n * Monotonic source of the internal, non-registry display id every\n * `SystemPrompt` carries. Anonymous (unnamed) prompts have nothing else to\n * identify them by; this id never feeds the registry and is never derived from\n * the wall clock, so it stays stable and order-deterministic across a run.\n */\nlet displayIdCounter = 0;\n\n/**\n * Narrow an arbitrary value to a `SystemPromptContract` — true when it exposes\n * the builder surface (`blocks` array + a callable `resolve`). Used by the\n * registry-aware `merge` overload to tell a folded contract from a raw block\n * or a registry name string, robustly across duplicate package copies.\n */\nfunction isSystemPromptContract(\n value: unknown,\n): value is SystemPromptContract {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { blocks?: unknown }).blocks) &&\n typeof (value as { resolve?: unknown }).resolve === \"function\"\n );\n}\n\n/**\n * Build the deterministic provenance label for a prompt — `name@version` when\n * it is registered, otherwise its internal display id. No random suffixes, so\n * the same source always yields the same `composedFrom` entry.\n */\nfunction provenanceLabel(prompt: SystemPromptContract): string {\n const meta = prompt.meta();\n\n if (meta?.name) {\n return promptKey(meta.name, meta.version ?? \"1\");\n }\n\n return prompt instanceof SystemPrompt ? prompt.id : \"anonymous\";\n}\n\n/**\n * Concrete `SystemPromptContract` — an immutable layered prompt builder.\n *\n * **Role.** The top-level composer for a system prompt: it holds an ordered\n * list of typed blocks (persona + instructions) and resolves the whole\n * stack into one final string when the agent is about to call the model.\n *\n * **Responsibility.**\n * - Owns: the ordered `blocks` list and the block-join rules (insertion\n * order, blank-line separator, trim).\n * - Does NOT own: how any individual block is rendered (delegated to each\n * block's `resolve()`), the placeholder syntax (delegated to\n * `renderPlaceholders`), or any knowledge of the agent, model, or\n * session consuming the resolved text.\n *\n * Blocks are discriminated by a string `type` tag (`\"persona\"` /\n * `\"instruction\"`) rather than `instanceof`, so user-supplied blocks that\n * implement `SystemPromptBlockContract` interoperate seamlessly with blocks\n * built via `ai.persona()` / `ai.instruction()` — even across duplicate\n * package copies or bundler scope boundaries.\n *\n * The builder is **immutable** — every `.persona()` / `.instruction()`\n * call returns a fresh `SystemPrompt` instance sharing nothing mutable\n * with its parent. This makes forking a base prompt into specialized\n * variants a safe, side-effect-free operation.\n *\n * Users construct via the `ai.systemPrompt()` factory — `new SystemPrompt()`\n * is not the public API (see §4.2 of code-style.md). Modeled as a class so\n * that methods live on the prototype (one copy shared across every forked\n * instance) and downstream code can branch via `instanceof SystemPrompt`.\n *\n * @example\n * // Chainable form\n * const alex = ai.persona(\"You are Alex, a TypeScript expert.\");\n * const replyIn = ai.instruction(\"Respond in {{language|English}}.\");\n *\n * const base = ai.systemPrompt().persona(alex).instruction(replyIn);\n * const arabicVariant = base.instruction(\"Prefer Arabic comments.\");\n *\n * base.resolve({ language: \"English\" });\n * arabicVariant.resolve({ language: \"Arabic\" });\n *\n * @example\n * // Array form — insertion order is preserved exactly\n * const prompt = ai.systemPrompt([\n * ai.persona(\"You are Alex, a TypeScript expert.\"),\n * ai.instruction(\"Respond in {{language|English}}.\"),\n * ]);\n */\nexport class SystemPrompt implements SystemPromptContract {\n /**\n * Internal, non-registry id for display / provenance. Stable for the life of\n * the instance; sourced from a monotonic counter, never the wall clock.\n * Anonymous prompts are identified solely by this id.\n */\n public readonly id: string;\n\n public constructor(\n public readonly blocks: readonly SystemPromptBlockContract[] = [],\n private readonly metaData?: SystemPromptMeta,\n ) {\n this.id = `prompt#${displayIdCounter++}`;\n\n // Auto-register the moment a builder acquires a name — whether through the\n // `systemPrompt(input, { name })` factory or a `.meta({ name })` rename.\n // Forks built by `persona()` / `instruction()` / `merge()` deliberately\n // drop the name (they pass no meta), so they stay anonymous and never land\n // in the registry unless explicitly re-named.\n if (metaData?.name) {\n defaultPromptsManager().register(this);\n }\n }\n\n /**\n * Read the current metadata snapshot (no argument) or derive a renamed\n * builder (with `meta`). The accessor returns `undefined` for an anonymous\n * prompt; the updater shallow-merges `meta` onto the current metadata and\n * returns a fresh builder. Naming the result registers it in `ai.prompts`.\n */\n public meta(): SystemPromptMeta | undefined;\n public meta(meta: SystemPromptMeta): SystemPromptContract;\n public meta(\n meta?: SystemPromptMeta,\n ): SystemPromptMeta | undefined | SystemPromptContract {\n if (meta === undefined) {\n return this.metaData;\n }\n\n return new SystemPrompt(this.blocks, { ...this.metaData, ...meta });\n }\n\n /**\n * Build a system prompt by reading the file at `path` once, synchronously,\n * at construction time. The file's UTF-8 contents seed a single instruction\n * block — the same semantics as the string-seed form of `systemPrompt()` —\n * so placeholders inside the file (`{{language|English}}`) resolve at\n * `resolve()` time and the result can be forked with further\n * `.persona()` / `.instruction()` calls.\n *\n * One-shot by design: the file is read exactly once here, never re-read on\n * `resolve()`. Reads are synchronous so the call stays a drop-in for the\n * synchronous `systemPrompt()` factory and the synchronous `resolve()` API.\n *\n * Throws `InvalidRequestError` when the file cannot be read (missing path,\n * permission denied) — surfacing the underlying cause so a typo in the\n * prompt path fails loudly at construction instead of silently producing an\n * empty prompt.\n *\n * @param path - Filesystem path to the prompt template file.\n *\n * @example\n * const prompt = SystemPrompt.fromFile(\"./prompts/support-agent.md\");\n *\n * const localized = prompt.instruction(\"Respond in {{language|English}}.\");\n * localized.resolve({ language: \"Arabic\" });\n */\n public static fromFile(path: string): SystemPrompt {\n let contents: string;\n\n try {\n contents = readFileSync(path, \"utf8\");\n } catch (error) {\n throw new InvalidRequestError(\n `Failed to read system prompt file \"${path}\" — ${\n error instanceof Error ? error.message : String(error)\n }`,\n { context: { path }, cause: error },\n );\n }\n\n return new SystemPrompt([new Instruction(contents)]);\n }\n\n /**\n * Return a new builder with the persona block set. If a persona already\n * exists it's replaced in place (preserving its position in `blocks`);\n * otherwise the new persona is prepended so persona-first remains the\n * default for chain-built prompts. Accepts either raw text (auto-wrapped\n * via `new Persona`) or an existing `PersonaContract` instance for reuse\n * across prompts.\n */\n public persona(value: PersonaContract | string): SystemPromptContract {\n const block = typeof value === \"string\" ? new Persona(value) : value;\n const existingIndex = this.blocks.findIndex(\n candidate => candidate.type === \"persona\",\n );\n\n if (existingIndex >= 0) {\n const next = [...this.blocks];\n next[existingIndex] = block;\n\n return new SystemPrompt(next) as this;\n }\n\n return new SystemPrompt([block, ...this.blocks]);\n }\n\n /**\n * Return a new builder with the given instruction appended. Instructions\n * render in insertion order. Accepts either raw text (auto-wrapped via\n * `new Instruction`) or an existing `InstructionContract` instance for\n * cross-prompt reuse.\n */\n public instruction(\n value: InstructionContract | string,\n ): SystemPromptContract {\n const block = typeof value === \"string\" ? new Instruction(value) : value;\n\n return new SystemPrompt([...this.blocks, block]);\n }\n\n /**\n * Fold predefined blocks, another prompt contract, or a registered prompt\n * name into this builder. Three forms share one method:\n *\n * - `merge(...blocks)` — N pre-built `ai.persona()` / `ai.instruction()`\n * blocks. A `persona` block sets/replaces the single, leading persona;\n * every other block appends in order. `base.merge(reviewer, style, lang)`\n * equals `base.persona(reviewer).instruction(style).instruction(lang)`.\n * - `merge(contract)` — another prompt; its blocks fold in (persona\n * replaces, instructions append) and `meta.composedFrom` records the\n * provenance of both sides.\n * - `merge(name, { fromVersion })` — a prompt resolved from `ai.prompts`\n * (latest version unless `fromVersion` selects another); throws\n * `InvalidRequestError` when the name / version is unregistered.\n *\n * Immutable — the original builder is untouched; passing zero blocks returns\n * an equivalent builder. The folded result is anonymous (no `name`), so it\n * is never auto-registered even though it carries `composedFrom` provenance.\n */\n public merge(\n ...blocks: readonly SystemPromptBlockContract[]\n ): SystemPromptContract;\n public merge(source: SystemPromptContract): SystemPromptContract;\n public merge(\n name: string,\n options?: SystemPromptMergeOptions,\n ): SystemPromptContract;\n public merge(\n first?:\n | SystemPromptBlockContract\n | SystemPromptContract\n | string,\n // `undefined` is part of the element union so the `merge(name, options?)`\n // overload's optional trailing `options?` (i.e. `… | undefined`) stays\n // assignable to this implementation signature.\n ...rest: readonly (\n | SystemPromptBlockContract\n | SystemPromptMergeOptions\n | undefined\n )[]\n ): SystemPromptContract {\n // Registry-name form: resolve from ai.prompts at the chosen version.\n if (typeof first === \"string\") {\n const options = rest[0] as SystemPromptMergeOptions | undefined;\n const resolved = defaultPromptsManager().get(first, options?.fromVersion);\n\n return this.mergeContract(resolved);\n }\n\n // Contract form: fold another prompt's blocks + record provenance.\n if (isSystemPromptContract(first)) {\n return this.mergeContract(first);\n }\n\n // Variadic block form (the original behavior).\n const all = [\n ...(first ? [first] : []),\n ...rest,\n ] as readonly SystemPromptBlockContract[];\n\n return this.foldBlocks(this, all);\n }\n\n /**\n * Fold an ordered list of blocks onto a starting prompt: persona blocks\n * set/replace the single leading persona; every other block appends in\n * order. The shared core of the variadic-block `merge` and the contract fold.\n */\n private foldBlocks(\n start: SystemPromptContract,\n blocks: readonly SystemPromptBlockContract[],\n ): SystemPromptContract {\n return blocks.reduce<SystemPromptContract>((prompt, block) => {\n if (block.type === \"persona\") {\n return prompt.persona(block as PersonaContract);\n }\n\n return new SystemPrompt([...prompt.blocks, block]);\n }, start);\n }\n\n /**\n * Fold another prompt contract into this one (persona replaces, instructions\n * append) and stamp the deterministic `composedFrom` provenance — this\n * prompt's existing provenance (or its own label) followed by the folded\n * source's label. The result is anonymous so it never auto-registers.\n */\n private mergeContract(\n source: SystemPromptContract,\n ): SystemPromptContract {\n const folded = this.foldBlocks(this, source.blocks);\n\n const baseProvenance =\n this.metaData?.composedFrom ??\n (this.metaData?.name ? [provenanceLabel(this)] : []);\n\n const composedFrom = [...baseProvenance, provenanceLabel(source)];\n\n // Carry forward only provenance — never the name — so the merged result is\n // a fresh anonymous prompt (immutable rename = new key; original stays).\n return new SystemPrompt(folded.blocks, { composedFrom });\n }\n\n /**\n * Resolve every block against the placeholder map, join the results with\n * blank-line separators (in insertion order), and trim. Returns an empty\n * string when no blocks are present — callers treat that as \"no system\n * message\".\n */\n public resolve(placeholders?: Placeholders): string {\n return this.blocks\n .map(block => block.resolve(placeholders))\n .join(\"\\n\\n\")\n .trim();\n }\n\n /**\n * Validate this prompt via the process-wide `ai.prompts` manager — sugar for\n * `ai.prompts.validate(this, options)`. Runs the deterministic placeholder\n * check and, when `options.judge` is supplied, the Nova-safe LLM-as-judge\n * pass. Never throws on a judge failure; `ok` tracks the deterministic\n * verdict alone.\n */\n public validate(\n options?: PromptsValidateOptions,\n ): Promise<PromptValidationResult> {\n return defaultPromptsManager().validate(this, options);\n }\n\n /**\n * Derive the compiled form of this prompt — a lazy wrapper that rewrites\n * the human-authored text into a model-optimized version on first use,\n * pins the result, and serves the pin thereafter. See\n * {@link RefinedSystemPromptContract} for the full semantics (lockfile\n * pinning, placeholder parity, advisory fallback, `refine()` /\n * `refinePrompt()`).\n *\n * The wrapper's collaborators are injected here rather than imported by\n * `refined-system-prompt.ts` — importing this module (or the prompts\n * manager) back from there would close an import cycle.\n */\n public refined(\n options: RefinedSystemPromptOptions,\n ): RefinedSystemPromptContract {\n return new RefinedSystemPrompt(this, options, {\n buildPrompt: (blocks, meta) => new SystemPrompt([...blocks], meta),\n validatePrompt: (target, validateOptions) =>\n defaultPromptsManager().validate(target, validateOptions),\n });\n }\n}\n\n/**\n * Public factory for `SystemPrompt`, callable directly or via its\n * `fromFile` static. Exists as a named interface so the callable signature\n * and the `fromFile` attachment travel together as one public type.\n */\nexport interface SystemPromptFactory {\n (\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n ): SystemPrompt;\n\n /**\n * Build a system prompt from a file read once at construction. Delegates\n * to {@link SystemPrompt.fromFile}, so `ai.systemPrompt.fromFile(path)` and\n * `SystemPrompt.fromFile(path)` behave identically.\n *\n * @example\n * const prompt = ai.systemPrompt.fromFile(\"./prompts/support-agent.md\");\n */\n fromFile(path: string): SystemPrompt;\n}\n\nfunction systemPromptFactory(\n input?: string | ReadonlyArray<SystemPromptBlockContract>,\n meta?: SystemPromptMeta,\n): SystemPrompt {\n if (input === undefined) {\n return new SystemPrompt([], meta);\n }\n\n if (typeof input === \"string\") {\n return new SystemPrompt([new Instruction(input)], meta);\n }\n\n return new SystemPrompt([...input], meta);\n}\n\n/**\n * Create a new immutable system-prompt builder.\n *\n * **Role.** Public factory for `SystemPrompt` — keeps user-facing code\n * free of `new` and consistent with `ai.tool()`, `ai.agent()`,\n * `ai.persona()`, `ai.instruction()`.\n *\n * Input forms:\n * - No argument → empty builder, chain `.persona()` / `.instruction()`\n * - Single string → seeded with one instruction for quick one-shot prompts\n * - Array of blocks → used verbatim, preserving insertion order\n * - `.fromFile(path)` → seeded from a file read once at construction\n *\n * Pass a second `meta` argument to name the prompt — a named prompt\n * auto-registers in `ai.prompts` under `name@version` (version defaults to the\n * next integer). Forks (`.persona()`, `.instruction()`, `.merge()`) are\n * anonymous unless re-named via `.meta({ name })`.\n *\n * @example\n * // Composed builder\n * const prompt = systemPrompt()\n * .persona(\"You are Alex, a senior TypeScript engineer.\")\n * .instruction(\"Always include working code examples.\")\n * .instruction(\"Respond in {{language|English}}.\");\n *\n * prompt.resolve({ language: \"Arabic\" });\n *\n * @example\n * // One-shot seed\n * const prompt = systemPrompt(\"Answer only with JSON matching the schema.\");\n *\n * @example\n * // From a file, read once at construction\n * const prompt = systemPrompt.fromFile(\"./prompts/support-agent.md\");\n *\n * @example\n * // Array form — fully declarative\n * const prompt = systemPrompt([\n * ai.persona(\"You are Alex.\"),\n * ai.instruction(\"Always cite sources.\"),\n * ai.instruction(\"Respond in {{language|English}}.\"),\n * ]);\n */\nexport const systemPrompt: SystemPromptFactory = Object.assign(\n systemPromptFactory,\n { fromFile: SystemPrompt.fromFile },\n);\n"],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -2439,7 +2439,7 @@ A bare `ai.supervisor()` / `ai.workflow()` uses a `snapshotStore` for `resume(ru
|
|
|
2439
2439
|
|
|
2440
2440
|
---
|
|
2441
2441
|
name: manage-prompts
|
|
2442
|
-
description: 'Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`; ''register a prompt by name'', ''resolve a prompt by name@version or tag'', ''pin a production tag to a prompt version'', ''diff two prompt versions'', ''export / import the prompt registry'', ''validate a prompt for missing placeholders'', ''merge a registered prompt into another''; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.'
|
|
2442
|
+
description: 'Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`, `criteria`; ''register a prompt by name'', ''resolve a prompt by name@version or tag'', ''pin a production tag to a prompt version'', ''diff two prompt versions'', ''export / import the prompt registry'', ''validate a prompt for missing placeholders'', ''validate a prompt against my own criteria / rules'', ''merge a registered prompt into another''; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.'
|
|
2443
2443
|
---
|
|
2444
2444
|
|
|
2445
2445
|
# `ai.prompts` — the unified prompt registry
|
|
@@ -2541,6 +2541,11 @@ const report = await ai.prompts.validate("support", {
|
|
|
2541
2541
|
placeholders: { product: "Warlock" }, // values you intend to supply
|
|
2542
2542
|
declare: ["language"], // extra keys to treat as known
|
|
2543
2543
|
judge: judgeModel, // optional — turns on the LLM-as-judge pass
|
|
2544
|
+
criteria: [ // optional — YOUR rules, replaces the built-in rubric
|
|
2545
|
+
"Addresses the user by {{name}}",
|
|
2546
|
+
"Never gives medical advice",
|
|
2547
|
+
"Stays under 200 words",
|
|
2548
|
+
],
|
|
2544
2549
|
});
|
|
2545
2550
|
|
|
2546
2551
|
report.ok; // true iff no required placeholder is missing (DETERMINISTIC verdict alone)
|
|
@@ -2551,6 +2556,7 @@ report.issues; // advisory judge reasons / a degrade note — present only whe
|
|
|
2551
2556
|
|
|
2552
2557
|
- **Always** runs the deterministic check: every `{{key}}` with no inline default that is neither supplied (`placeholders`), declared (`declare`), nor in the prompt's `meta.required` lands in `missing`; `ok` is `true` iff `missing` is empty.
|
|
2553
2558
|
- **`judge`** adds a **Nova-safe** LLM-as-judge quality pass — it **never throws** and degrades to an `issues` note (leaving `score` undefined) on failure, so a flaky judge can **never flip `ok`**.
|
|
2559
|
+
- **`criteria`** (a string or a list of short rules) grades the prompt against **your own rules** instead of the built-in quality rubric — `score` / `issues` then reflect your criteria (a failed rule is named in `issues`). Only used when `judge` is also set; folded into the `judgeCache` key so different rules re-run. Still advisory — never flips `ok`.
|
|
2554
2560
|
- **`target`** is a registered name (or `name@selector`), a `SystemPromptContract` instance, or a raw prompt string.
|
|
2555
2561
|
- **`judgeCache`** (per-call or via the `prompts({ judgeCache })` factory option) memoizes judge verdicts by a content hash of the resolved body + the judge model id — a structural `{ get, set }` subset of `@warlock.js/cache`'s `CacheDriver`, so the cache package stays a strictly **optional** peer.
|
|
2556
2562
|
|
|
@@ -2621,6 +2627,7 @@ If you only ever called `ai.prompt({ ... })` and used the returned registry, **n
|
|
|
2621
2627
|
## See also
|
|
2622
2628
|
|
|
2623
2629
|
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — the `systemPrompt()` / `persona()` / `instruction()` builder, `.meta()`, and `merge()` this registry stores and composes
|
|
2630
|
+
- [`@warlock.js/ai/refine-prompts/SKILL.md`](@warlock.js/ai/refine-prompts/SKILL.md) — `systemPrompt().refined({ model, criteria, store })`, the prompt compiler; register its `refinePrompt()` output as a next version to `diff` original vs refined
|
|
2624
2631
|
- [`@warlock.js/ai/eval-datasets-and-ci/SKILL.md`](@warlock.js/ai/eval-datasets-and-ci/SKILL.md) — the eval `judge` scorer `validate()`'s LLM pass reuses
|
|
2625
2632
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — wiring a resolved prompt into an agent, plus the judge-safe agent preset (`ai.agent.judge`)
|
|
2626
2633
|
|
|
@@ -3411,6 +3418,101 @@ VCR composes **below** `ai.fallbackModel` and works with any adapter. Wrap the l
|
|
|
3411
3418
|
- [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) — the adapters whose models VCR wraps
|
|
3412
3419
|
|
|
3413
3420
|
|
|
3421
|
+
## refine-prompts `@warlock.js/ai/refine-prompts/SKILL.md`
|
|
3422
|
+
|
|
3423
|
+
---
|
|
3424
|
+
name: refine-prompts
|
|
3425
|
+
description: 'Prompt compiler — systemPrompt(...).refined({ model, criteria, store }): humans keep writing human prompt text (dev code or admin-panel textareas); the refined wrapper lazily rewrites it into a model-optimized version via a refiner model on first agent use, pins the result (lockfile posture — recompiled ONLY when the source text, refiner model, criteria, or built-in recipe version change, never silently over time), and serves the pin thereafter. Explicit surfaces: await refined.refine() → the compiled template STRING (placeholders intact — for admin routes, previews, boot warmup, CI; throws PromptRefinementError on failure) and await refined.refinePrompt() → a composable SystemPromptContract with meta.refinedFrom / meta.refinerModel provenance (register it as a next version to unlock ai.prompts.diff review). Placeholder parity is machine-enforced (the exact {{placeholder}} set must survive or the rewrite is rejected after one repair re-ask); the lazy agent path NEVER throws — on refiner failure it warns once and serves the original. store is a structural { get, set } (any @warlock.js/cache CacheDriver); omitted ⇒ the pin lives on the instance for the process lifetime. Triggers: `refined`, `refine`, `refinePrompt`, `materialize`, `RefinedSystemPromptContract`, `RefinedSystemPromptOptions`, `RefinedPromptStoreLike`, `PromptRefineOptions`, `PromptRefinementError`, `refinedFrom`, `refinerModel`, `fresh`, `prompt-refiner`, ''refine a prompt'', ''compile a prompt'', ''optimize a system prompt'', ''rewrite my prompt to be AI-friendly'', ''admin-written prompts'', ''prompt refinement store''; typical import `import { ai } from "@warlock.js/ai"`. Skip: registry operations (register / resolve / tag / diff / validate) — `@warlock.js/ai/manage-prompts/SKILL.md`; composing prompts from persona + instruction blocks — `@warlock.js/ai/write-system-prompt/SKILL.md`; grading a prompt against rules without rewriting it — validate({ criteria }) in `@warlock.js/ai/manage-prompts/SKILL.md`.'
|
|
3426
|
+
---
|
|
3427
|
+
|
|
3428
|
+
# `systemPrompt().refined()` — the prompt compiler
|
|
3429
|
+
|
|
3430
|
+
Humans write prompts as human text; models perform better on structured, model-tuned phrasing. `.refined({ model, criteria, store })` turns any `SystemPrompt` into a **lazily-compiled artifact**: the first agent use rewrites the raw source template through the refiner `model`, pins the result, and every later use serves the pin. The human text stays the editing surface forever — the refined text is a derived artifact, like a lockfile.
|
|
3431
|
+
|
|
3432
|
+
```ts
|
|
3433
|
+
import { ai } from "@warlock.js/ai";
|
|
3434
|
+
|
|
3435
|
+
const support = ai
|
|
3436
|
+
.systemPrompt(
|
|
3437
|
+
[ai.persona("You are a friendly assistant."), ai.instruction("Help {{name}} with orders.")],
|
|
3438
|
+
{ name: "support" },
|
|
3439
|
+
)
|
|
3440
|
+
.refined({ model: refinerModel, store: myCacheDriver });
|
|
3441
|
+
|
|
3442
|
+
// Lazy: compiles on the first run, serves the pin afterwards.
|
|
3443
|
+
const agent = ai.agent({ model, systemPrompt: support });
|
|
3444
|
+
|
|
3445
|
+
// Explicit: compile now — admin routes, previews, boot warmup, CI.
|
|
3446
|
+
const text = await support.refine(); // the compiled template STRING
|
|
3447
|
+
const prompt = await support.refinePrompt(); // a composable SystemPromptContract
|
|
3448
|
+
```
|
|
3449
|
+
|
|
3450
|
+
## The four trust rules
|
|
3451
|
+
|
|
3452
|
+
1. **Lockfile posture.** The pin key hashes the recipe version + refiner model + `criteria` + source template — any input change compiles fresh; an unchanged input NEVER recompiles (no TTL, no silent drift). `store` is a **store, not a cache**.
|
|
3453
|
+
2. **Prose, never contract.** The exact `{{placeholder}}` set (name **and** `|default`) must survive the rewrite verbatim — checked mechanically; a parity break gets ONE repair re-ask, then the rewrite is rejected. The compiled text is still a **template**: placeholders resolve per call as usual.
|
|
3454
|
+
3. **Advisory with fallback.** The lazy agent path never throws: a refiner failure warns once (`[warlock-ai] …`) and serves the ORIGINAL prompt — the human text is always a valid prompt. After **3** failed attempts the lazy path stops retrying for the instance lifetime (no per-run refiner latency from a broken key/provider); the explicit `refine()` / `refinePrompt()` stay live — they **throw** `PromptRefinementError` (`error.reason`: `"model"` / `"parity"` / `"empty"`) and a later success re-arms the pin for everyone.
|
|
3455
|
+
4. **Reviewable.** `refine()` exposes the compiled text; `refinePrompt()` makes it a first-class prompt with provenance.
|
|
3456
|
+
|
|
3457
|
+
## `refine(options?)` — the explicit string surface
|
|
3458
|
+
|
|
3459
|
+
```ts
|
|
3460
|
+
const text = await support.refine(); // store-first; pins on first compile
|
|
3461
|
+
const another = await support.refine({ fresh: true }); // skip the pin, new take, re-pins
|
|
3462
|
+
```
|
|
3463
|
+
|
|
3464
|
+
Expose it via a route for an admin **preview / approve** flow — the admin sees original vs refined, and the call itself warms the pin so the next agent run pays nothing. Also the boot-warmup / CI-compile surface.
|
|
3465
|
+
|
|
3466
|
+
## `refinePrompt(options?)` — the composable surface
|
|
3467
|
+
|
|
3468
|
+
```ts
|
|
3469
|
+
const compiled = await support.refinePrompt();
|
|
3470
|
+
|
|
3471
|
+
compiled.blocks; // one instruction block = the refined template
|
|
3472
|
+
compiled.meta()?.refinedFrom; // "support@1" (or "anonymous")
|
|
3473
|
+
compiled.meta()?.refinerModel; // "anthropic:claude-sonnet-4-5"
|
|
3474
|
+
compiled.meta()?.required; // carried from the source — contract preserved
|
|
3475
|
+
```
|
|
3476
|
+
|
|
3477
|
+
It never auto-registers (no `name` — registry versions stay human-intentional). Register it deliberately to unlock the review flow:
|
|
3478
|
+
|
|
3479
|
+
```ts
|
|
3480
|
+
compiled.meta({ name: "support" }); // registers as support@<next>
|
|
3481
|
+
ai.prompts.diff("support", "1", "2"); // original vs refined, block by block
|
|
3482
|
+
```
|
|
3483
|
+
|
|
3484
|
+
## Options
|
|
3485
|
+
|
|
3486
|
+
- **`model`** (required) — the refiner `ModelContract`. The call runs as a one-shot `"prompt-refiner"` agent, so usage/cost surface through the standard report/observer machinery.
|
|
3487
|
+
- **`criteria`** — a string or list of rules the rewrite MUST satisfy, on top of the built-in recipe. Same word and shape as `validate({ criteria })`: *validate grades against criteria; refined rewrites against them*. Folded into the pin key — new rules compile fresh.
|
|
3488
|
+
- **`store`** — structural `{ get, set }` (`RefinedPromptStoreLike`; any `@warlock.js/cache` `CacheDriver` satisfies it — the cache package stays an optional peer). Share a redis/pg-backed driver so ONE process pays each compilation and the fleet reads the pin. Omitted ⇒ the pin lives on the wrapper instance for the process lifetime. A pinned value that fails the parity check (corrupt / tampered store) is treated as a miss and recompiled.
|
|
3489
|
+
|
|
3490
|
+
## What compiles where — the lazy boundary
|
|
3491
|
+
|
|
3492
|
+
The lazy compile hook rides the **agent path** (`ai.agent` execute/stream, and everything built on it — supervisors' member agents, planner steps, eval, `spawnSubAgent`, `serve`). Prompts resolved **synchronously at factory time** — `ai.planner({ systemPrompt })` / `ai.router({ systemPrompt })` prefixes, a supervisor's own `systemPrompt` / `goal`, and `ai.prompts.resolve()` — use the ORIGINAL text unless you pre-warm:
|
|
3493
|
+
|
|
3494
|
+
```ts
|
|
3495
|
+
await refined.refine(); // warm the pin at boot…
|
|
3496
|
+
const planner = ai.planner({ systemPrompt: refined, ... }); // …then factories see it? NO —
|
|
3497
|
+
```
|
|
3498
|
+
|
|
3499
|
+
Factory-time resolution reads whatever is pinned **at that moment** — so warm BEFORE constructing the factory, or pass `await refined.refinePrompt()` instead (an already-compiled plain prompt).
|
|
3500
|
+
|
|
3501
|
+
## Chaining and identity
|
|
3502
|
+
|
|
3503
|
+
- `refined.meta()` reads the SOURCE meta — agent reports stamp the source `name@version`, so observability groups by the prompt you authored.
|
|
3504
|
+
- `.persona()` / `.instruction()` / `.merge()` / `.meta({...})` derive a NEW source and re-wrap it with the same refinement options — editing a compiled prompt invalidates its pin naturally (new source ⇒ new key).
|
|
3505
|
+
- `refined.source` is always the original builder; `refined.resolve(placeholders)` serves the compiled text once pinned, the original before.
|
|
3506
|
+
- **Register the source or the `refinePrompt()` output — not the wrapper itself.** The wrapper's `blocks` flip from source to compiled text on materialization, so `ai.prompts.register(wrapper)` would fingerprint whatever is pinned at call time (and a re-register after the flip throws on the content mismatch).
|
|
3507
|
+
- `validate()` on the wrapper validates what it currently serves — pair `refined` with `validate({ criteria, judge })` to lint the compiled text, and with `agent.eval` (original vs refined on a dataset) to PROVE the rewrite helps before trusting it.
|
|
3508
|
+
|
|
3509
|
+
## See also
|
|
3510
|
+
|
|
3511
|
+
- [`@warlock.js/ai/manage-prompts/SKILL.md`](@warlock.js/ai/manage-prompts/SKILL.md) — the registry (`name@version`, tags, `diff`, `validate({ criteria })`) the review flow rides on
|
|
3512
|
+
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — the `systemPrompt()` builder `.refined()` extends
|
|
3513
|
+
- [`@warlock.js/ai/eval-datasets-and-ci/SKILL.md`](@warlock.js/ai/eval-datasets-and-ci/SKILL.md) — measure original vs refined behaviour on a dataset
|
|
3514
|
+
|
|
3515
|
+
|
|
3414
3516
|
## run-ai-agent `@warlock.js/ai/run-ai-agent/SKILL.md`
|
|
3415
3517
|
|
|
3416
3518
|
---
|
|
@@ -5560,6 +5662,7 @@ Three distinct prompts, one common foundation. Base is immutable — safe to sha
|
|
|
5560
5662
|
## See also
|
|
5561
5663
|
|
|
5562
5664
|
- [`@warlock.js/ai/manage-prompts/SKILL.md`](@warlock.js/ai/manage-prompts/SKILL.md) — the `ai.prompts` registry these named prompts auto-register into (resolve / version / tag / diff / export / validate)
|
|
5665
|
+
- [`@warlock.js/ai/refine-prompts/SKILL.md`](@warlock.js/ai/refine-prompts/SKILL.md) — `.refined({ model, criteria, store })`, the prompt compiler: lazily rewrite this builder into a model-optimized version, pinned like a lockfile
|
|
5563
5666
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `systemPrompt` on factory + per-call override
|
|
5564
5667
|
- [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — per-step agent references inherit their own system prompt
|
|
5565
5668
|
|
package/llms.txt
CHANGED
|
@@ -23,12 +23,13 @@
|
|
|
23
23
|
- [handle-ai-errors](@warlock.js/ai/handle-ai-errors/SKILL.md): Typed AIError hierarchy with stable code strings + coarse category for retry-policy dispatch. execute() never throws — errors surface via result.error (the sole exception: OrchestratorConfigError throws at construction). Triggers: `AIError`, `ProviderRateLimitError`, `ProviderAuthError`, `ContextLengthExceededError`, `ContentFilterError`, `SchemaValidationError`, `ToolExecutionError`, `WorkflowDriftError`, `SupervisorDriftError`, `SupervisorFailedError`, `SupervisorRoutingError`, `OrchestratorFailedError`, `OrchestratorDriftError`, `OrchestratorConfigError`, `OrchestratorCancelledError`, `PlannerFailedError`, `PlannerPlanInvalidError`, `PlannerCancelledError`, `BudgetExceededError`, `GuardrailViolationError`, `error.code`, `error.category`; 'handle ai error', 'retry on rate limit', 'branch on error code', 'ORCHESTRATOR_DRIFT', 'PLANNER_PLAN_INVALID', 'build fallback ladder'; typical import `import { AIError } from "@warlock.js/ai"`. Skip: log surfacing — `@warlock.js/ai/log-ai-calls/SKILL.md`; native `try / catch` on raw `openai`.
|
|
24
24
|
- [log-ai-calls](@warlock.js/ai/log-ai-calls/SKILL.md): Framework logging delegated to @warlock.js/logger — every primitive emits via the log singleton, configure channels / levels / redaction once at boot. Four-arg call convention (module, action, message, context). Triggers: `log.configure`, `log.setMinLevel`, `log.setChannels`, `ConsoleLog`, `FileLog`, `LogChannel`, `redact.paths`, `ai.agent.<name>` / `ai.workflow.<name>` / `ai.supervisor.<name>` modules; 'configure ai logging', 'mask prompts in logs', 'silence logs in tests', 'capture log entries'; typical import `import { log } from "@warlock.js/logger"`. Skip: error hierarchy — `@warlock.js/ai/handle-ai-errors/SKILL.md`; competing libs `pino`, `winston`, `console.log`.
|
|
25
25
|
- [manage-ai-stores](@warlock.js/ai/manage-ai-stores/SKILL.md): Durable orchestrator stores — ai.checkpoint.{memory,pg,redis}() for cross-turn SESSION STATE and ai.snapshot.{memory,pg,redis}() for in-flight SUPERVISOR/WORKFLOW run state. Two distinct contracts (CheckpointStore vs SnapshotStore), dev-owned pg/redis clients (no peer dep), never-auto-migrated schema(), global defaults via ai.config({defaultCheckpointStore, defaultSnapshotStore}). Triggers: `ai.checkpoint`, `ai.snapshot`, `checkpointStore`, `snapshotStore`, `CheckpointStore`, `SnapshotStore`, `CheckpointRecord`, `checkpoint.pg`, `checkpoint.redis`, `snapshot.pg`, `snapshot.redis`, `store.schema()`, `keepSnapshots`, `defaultCheckpointStore`, `defaultSnapshotStore`, `PgClientLike`, `RedisClientLike`; 'persist orchestrator sessions', 'wire a pg checkpoint store', 'run the store DDL', 'checkpoint vs snapshot'; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator lifecycle — `@warlock.js/ai/run-orchestrator/SKILL.md`; cache-backed snapshot resume / semanticCache store — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `temporal`, `inngest`.
|
|
26
|
-
- [manage-prompts](@warlock.js/ai/manage-prompts/SKILL.md): Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`; 'register a prompt by name', 'resolve a prompt by name@version or tag', 'pin a production tag to a prompt version', 'diff two prompt versions', 'export / import the prompt registry', 'validate a prompt for missing placeholders', 'merge a registered prompt into another'; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.
|
|
26
|
+
- [manage-prompts](@warlock.js/ai/manage-prompts/SKILL.md): Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`, `criteria`; 'register a prompt by name', 'resolve a prompt by name@version or tag', 'pin a production tag to a prompt version', 'diff two prompt versions', 'export / import the prompt registry', 'validate a prompt for missing placeholders', 'validate a prompt against my own criteria / rules', 'merge a registered prompt into another'; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.
|
|
27
27
|
- [observe-ai-flows](@warlock.js/ai/observe-ai-flows/SKILL.md): The core Observer seam — a generic, tool-agnostic observability hook every flow routes its completed ExecutionReport through. Covers the per-flow `observe?: boolean | Observer` option on ai.agent / workflow / supervisor / team, the global registry (registerObserver / getObservers / setObserveAll / isObserveAll / clearObservers), resolveObservers / notifyObservers resolution, the opt-in AgentConfig.captureMessages → AgentReport.messages full-history capture, the onConfigApplied dependency-inversion seam, and that @warlock.js/ai-panoptic is the batteries-included Observer. Triggers: `Observer`, `observe`, `registerObserver`, `getObservers`, `setObserveAll`, `isObserveAll`, `clearObservers`, `resolveObservers`, `notifyObservers`, `FlowObserveOption`, `ExecutionReport`, `captureMessages`, `AgentReport.messages`, `CapturedMessage`, `onConfigApplied`, `observeAll`; 'observe an agent run', 'send finished reports to a collector', 'capture the full message history', 'observe every flow by default', 'wire panoptic / tracing'; typical import `import { ai, registerObserver } from "@warlock.js/ai"`. Skip: structured logging of events — `@warlock.js/ai/log-ai-calls/SKILL.md`; reading the report tree shape (trips / children) — `@warlock.js/ai/run-ai-agent/SKILL.md`; per-call cost / usage rollup — `@warlock.js/ai/handle-ai-errors/SKILL.md`. The batteries-included Observer is the `@warlock.js/ai-panoptic` package.
|
|
28
28
|
- [persist-ai-data](@warlock.js/ai/persist-ai-data/SKILL.md): Persistence delegated to @warlock.js/cache — workflow + supervisor snapshot resume via snapshotStore (4.3.0: now a SnapshotStore from ai.snapshot.*, ⚠ moved off raw CacheDriver), semantic cache + memory via vector-capable CacheDriver, global defaults via ai.config({defaultStore}) + ai.config({defaultSnapshotStore}). Covers drift detection + three recovery paths. Triggers: `ai.config`, `defaultStore`, `defaultSnapshotStore`, `snapshotStore`, `ai.snapshot`, `wf.resume`, `supervisor.resume`, `WorkflowSnapshot`, `SupervisorSnapshot`, `WorkflowDriftError`, `SupervisorDriftError`, `force: true`; 'resume a workflow run', 'configure snapshot store', 'handle signature drift', 'wire pg vector cache'; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator checkpoint/snapshot store factories — `@warlock.js/ai/manage-ai-stores/SKILL.md`; cache driver catalog — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `temporal`, `inngest`.
|
|
29
29
|
- [pick-ai-provider](@warlock.js/ai/pick-ai-provider/SKILL.md): Choose an AI provider adapter — @warlock.js/ai-openai (shipped, also handles OpenRouter / Azure via baseURL), @warlock.js/ai-anthropic, @warlock.js/ai-bedrock, @warlock.js/ai-google, @warlock.js/ai-ollama — plus cost truth: ModelPricing (per-1M tokens), Usage cost breakdown, the cachedTokens / cacheWriteTokens / reasoningTokens channels, and capability flags. Triggers: `OpenAISDK`, `SDKAdapterContract`, `ModelContract`, `ModelPricing`, `ModelCapabilities`, `sdk.model`, `sdk.embedder`, `capabilities.vision`, `capabilities.structuredOutput`, `capabilities.reasoning`, `capabilities.promptCaching`, `pricing`, `Usage.cost`, `cachedTokens`, `cacheWriteTokens`, `reasoningTokens`, `reasoning.effort`, `cacheControl`, `baseURL`, `provider: "openrouter"`; 'pick a provider', 'openai vs openrouter', 'does this model support vision/reasoning', 'configure pricing', 'how much did reasoning cost', 'prompt cache tokens'; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: agent factory — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs raw `openai`, `@anthropic-ai/sdk`, `@aws-sdk/client-bedrock-runtime`.
|
|
30
30
|
- [rag-loaders-and-stores](@warlock.js/ai/rag-loaders-and-stores/SKILL.md): Turn any source into a RagDocument and index it in a production vector store — the document loaders ai.rag.loadText / loadHtml / loadWeb (SSRF-safe via guardedFetch) / loadPdf (lazy pdf-parse peer), plus the swappable stores ai.rag.pgVectorStore({client}) (pgvector + ensureSchema DDL + hnsw/ivfflat index) and ai.rag.cacheVectorStore(driver), both satisfying VectorStoreContract (upsert / query / removeNamespace). Loaders return the exact RagDocument[] that kb.index() consumes — no adapter. Triggers: `ai.rag.loadText`, `ai.rag.loadHtml`, `ai.rag.loadWeb`, `ai.rag.loadPdf`, `loadText`, `loadHtml`, `loadWeb`, `loadPdf`, `ai.rag.pgVectorStore`, `ai.rag.cacheVectorStore`, `pgVectorStore`, `cacheVectorStore`, `VectorStore`, `PgVectorStoreOptions`, `PgVectorStoreInstance`, `ensureSchema`, `schema()`, `RagLoaderResult`, `LoadWebOptions`, `LoadPdfOptions`, `perPage`, `OutboundPolicy`, `guardedFetch`, `hnsw`, `ivfflat`, `pgvector`, `dimensions`, `PgClientLike`, `PDF_PARSE_INSTALL_INSTRUCTIONS`; 'load a website into a knowledge base', 'index a PDF for RAG', 'strip HTML to text for embedding', 'pgvector store for RAG', 'SSRF-safe document fetch', 'one document per PDF page', 'swap the vector store'; typical import `import { ai } from "@warlock.js/ai"`. Skip: the chunk → embed → retrieve → rerank → cite pipeline that consumes these — `@warlock.js/ai/run-ai-rag/SKILL.md`; the raw embedder primitive — `@warlock.js/ai/embed-text/SKILL.md`; cache similarity internals — `@warlock.js/cache/use-cache-similarity/SKILL.md`; competing libs `langchain` loaders, `llamaindex` readers.
|
|
31
31
|
- [record-replay-llm](@warlock.js/ai/record-replay-llm/SKILL.md): Deterministic, offline LLM tests with ai.vcr(model,{path,mode}) — a record/replay decorator over ANY ModelContract that intercepts only complete()/stream(), delegates name/provider/capabilities/pricing to the inner model, and hashes each request against a JSON cassette on disk. Covers the three modes (record / replay / auto), the cassette format, save(), VcrCassetteMissError, streaming round-trip, hashOptions, and composing below fallbackModel. Triggers: `ai.vcr`, `vcr`, `VcrModel`, `VcrOptions`, `VcrMode`, `Cassette`, `CassetteEntry`, `VcrCassetteMissError`, `hashRequest`, `DEFAULT_HASH_OPTIONS`, `mode`, `path`, `hashOptions`, `save`, `cassette`, record, replay, cassette; 'record LLM responses for tests', 'replay model calls offline in CI', 'deterministic agent test without hitting the provider', 'cassette for model calls'; typical import `import { ai } from "@warlock.js/ai"`. Skip: eval scoring + regression gating — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; the Vitest matchers + mockRouter — `@warlock.js/ai/ai-dx-helpers/SKILL.md`; choosing a provider adapter — `@warlock.js/ai/pick-ai-provider/SKILL.md`; competing libs `nock`, `polly.js`.
|
|
32
|
+
- [refine-prompts](@warlock.js/ai/refine-prompts/SKILL.md): Prompt compiler — systemPrompt(...).refined({ model, criteria, store }): humans keep writing human prompt text (dev code or admin-panel textareas); the refined wrapper lazily rewrites it into a model-optimized version via a refiner model on first agent use, pins the result (lockfile posture — recompiled ONLY when the source text, refiner model, criteria, or built-in recipe version change, never silently over time), and serves the pin thereafter. Explicit surfaces: await refined.refine() → the compiled template STRING (placeholders intact — for admin routes, previews, boot warmup, CI; throws PromptRefinementError on failure) and await refined.refinePrompt() → a composable SystemPromptContract with meta.refinedFrom / meta.refinerModel provenance (register it as a next version to unlock ai.prompts.diff review). Placeholder parity is machine-enforced (the exact {{placeholder}} set must survive or the rewrite is rejected after one repair re-ask); the lazy agent path NEVER throws — on refiner failure it warns once and serves the original. store is a structural { get, set } (any @warlock.js/cache CacheDriver); omitted ⇒ the pin lives on the instance for the process lifetime. Triggers: `refined`, `refine`, `refinePrompt`, `materialize`, `RefinedSystemPromptContract`, `RefinedSystemPromptOptions`, `RefinedPromptStoreLike`, `PromptRefineOptions`, `PromptRefinementError`, `refinedFrom`, `refinerModel`, `fresh`, `prompt-refiner`, 'refine a prompt', 'compile a prompt', 'optimize a system prompt', 'rewrite my prompt to be AI-friendly', 'admin-written prompts', 'prompt refinement store'; typical import `import { ai } from "@warlock.js/ai"`. Skip: registry operations (register / resolve / tag / diff / validate) — `@warlock.js/ai/manage-prompts/SKILL.md`; composing prompts from persona + instruction blocks — `@warlock.js/ai/write-system-prompt/SKILL.md`; grading a prompt against rules without rewriting it — validate({ criteria }) in `@warlock.js/ai/manage-prompts/SKILL.md`.
|
|
32
33
|
- [run-ai-agent](@warlock.js/ai/run-ai-agent/SKILL.md): Build agents with ai.agent({...}) — the single-LLM-turn primitive. Covers execute / stream, attachments, structured output, placeholders, events, agent.eval scoring, the judge-safe preset for resilient LLM-as-judge / verdict classifiers (ai.agent.judge / judge: true — lenient JSON parse + repair + never-throw, for Nova-class models), and auto-adapting raw executables in tools:[]. Triggers: `ai.agent`, `ai.agent.judge`, `agent.execute`, `agent.stream`, `agent.eval`, `AgentResult`, `AgentReport`, `AgentToolEntry`, `JudgeConfig`, `JudgeAgentConfig`, `judge`, `repairAttempts`, `streamingToolGuard`, `attachments`, `repair`, `maxTrips`, `sessionId`, `spawnSubAgent`, `SpawnSubAgentSpec`; 'run an agent', 'stream an agent response', 'structured output schema', 'pass image to agent', 'evaluate an agent', 'LLM-as-judge that survives malformed JSON', 'grade with a Nova model without crashing', 'put a supervisor in tools', 'cancel an agent run', 'spawn a one-shot sub-agent with a per-task budget'; typical import `import { ai } from "@warlock.js/ai"`. Skip: tool definition — `@warlock.js/ai/define-ai-tool/SKILL.md`; workflows — `@warlock.js/ai/run-ai-workflow/SKILL.md`; eval matchers / batch / fallback detail — `@warlock.js/ai/ai-dx-helpers/SKILL.md`; competing libs `langchain`, `ai` (Vercel), raw `openai`.
|
|
33
34
|
- [run-ai-rag](@warlock.js/ai/run-ai-rag/SKILL.md): Retrieval-augmented generation with ai.rag({...}) — a chunk → embed → vector-store → retrieve → rerank → cite pipeline that reuses ai.embedder + a @warlock.js/cache CacheDriver. Covers index() / retrieve() / clear() / asTool(), chunking strategies (recursive | markdown | sentence | fixed), Citation / RetrievedChunk provenance, and the opt-in rerankers ai.rag.keywordReranker / ai.rag.llmReranker. Triggers: `ai.rag`, `rag.index`, `rag.retrieve`, `rag.clear`, `rag.asTool`, `RagConfig`, `RagDocument`, `RetrieveOptions`, `RetrieveResult`, `RetrievedChunk`, `Citation`, `ChunkOptions`, `ChunkType`, `ai.rag.keywordReranker`, `ai.rag.llmReranker`, `cacheVectorStore`, `VectorStore`, `topK`, `threshold`, `candidates`; 'build a knowledge base', 'retrieve relevant chunks for a query', 'cite the source of an answer', 'chunk markdown for embedding', 'rerank retrieval results', 'expose retrieval as a tool'; typical import `import { ai } from "@warlock.js/ai"`. Skip: raw single-string embedding — `@warlock.js/ai/embed-text/SKILL.md`; exact + vector LLM-response cache — `@warlock.js/ai/attach-ai-middleware/SKILL.md` (ai.middleware.semanticCache); tool wiring — `@warlock.js/ai/define-ai-tool/SKILL.md`; competing libs `langchain`, `llamaindex`.
|
|
34
35
|
- [run-ai-team](@warlock.js/ai/run-ai-team/SKILL.md): Manager-led multi-agent teams with ai.team({...}) — transparent sugar over ai.supervisor that maps a manager → route/router, members → intents, and a gate → evaluate, returning a REAL SupervisorContract (no new loop, no new contract). Covers the built-in gate strings "quality" (review-then-fix) and "verify" (test-then-fix), a custom gate function, role mapping (roles / gateKey), and the verbatim supervisor pass-throughs (goal / output / state / maxIterations / snapshotStore / on / observe). Triggers: `ai.team`, `TeamConfig`, `TeamGate`, `TeamGateFn`, `TeamMemberValue`, `manager`, `members`, `gate`, `roles`, `gateKey`, `buildQualityGate`, `buildVerifyGate`, `SupervisorContract`, `ReportType`; 'build a team of agents', 'manager that delegates to members', 'review then fix loop', 'test then fix loop', 'quality gate for a multi-agent run', 'report type team'; typical import `import { ai } from "@warlock.js/ai"`. Skip: routing one input to a fixed roster directly — `@warlock.js/ai/run-supervisor/SKILL.md` (team is sugar over it); durable cross-turn sessions — `@warlock.js/ai/run-orchestrator/SKILL.md`; LLM-generated plans — `@warlock.js/ai/run-planner/SKILL.md`; competing libs `crewai`, `autogen`.
|
package/package.json
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"@standard-schema/spec": "^1.0.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@warlock.js/cache": "4.
|
|
19
|
-
"@warlock.js/logger": "4.
|
|
18
|
+
"@warlock.js/cache": "4.7.0",
|
|
19
|
+
"@warlock.js/logger": "4.7.0",
|
|
20
20
|
"langfuse": "*",
|
|
21
21
|
"openai": "*",
|
|
22
22
|
"pg": "*",
|
|
23
23
|
"redis": "*"
|
|
24
24
|
},
|
|
25
|
-
"version": "4.
|
|
25
|
+
"version": "4.7.0",
|
|
26
26
|
"main": "./cjs/index.cjs",
|
|
27
27
|
"module": "./esm/index.mjs",
|
|
28
28
|
"types": "./esm/index.d.mts",
|
package/skills/README.md
CHANGED
|
@@ -72,6 +72,10 @@ Choose an AI provider adapter — @warlock.js/ai-openai (shipped, also handles O
|
|
|
72
72
|
|
|
73
73
|
Deterministic, offline LLM tests with ai.vcr(model,{path,mode}) — a record/replay decorator over ANY ModelContract that intercepts only complete()/stream(), delegates name/provider/capabilities/pricing to the inner model, and hashes each request against a JSON cassette on disk. Covers the three modes (record / replay / auto), the cassette format, save(), VcrCassetteMissError, streaming round-trip, hashOptions, and composing below fallbackModel. Triggers: `ai.vcr`, `vcr`, `VcrModel`, `VcrOptions`, `VcrMode`, `Cassette`, `CassetteEntry`, `VcrCassetteMissError`, `hashRequest`, `DEFAULT_HASH_OPTIONS`, `mode`, `path`, `hashOptions`, `save`, `cassette`, record, replay, cassette; 'record LLM responses for tests', 'replay model calls offline in CI', 'deterministic agent test without hitting the provider', 'cassette for model calls'; typical import `import { ai } from "@warlock.js/ai"`. Skip: eval scoring + regression gating — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; the Vitest matchers + mockRouter — `@warlock.js/ai/ai-dx-helpers/SKILL.md`; choosing a provider adapter — `@warlock.js/ai/pick-ai-provider/SKILL.md`; competing libs `nock`, `polly.js`.
|
|
74
74
|
|
|
75
|
+
### [`refine-prompts/`](./refine-prompts/SKILL.md)
|
|
76
|
+
|
|
77
|
+
Prompt compiler — systemPrompt(...).refined({ model, criteria, store }): humans keep writing human prompt text; the refined wrapper lazily rewrites it into a model-optimized version on first agent use, pins the result (lockfile posture — recompiled only when source / model / criteria / recipe change), and serves the pin thereafter. `await refined.refine()` returns the compiled template string (routes / previews / warmup / CI; throws PromptRefinementError); `await refined.refinePrompt()` returns a composable prompt with meta.refinedFrom provenance (register it to diff original vs refined). Placeholder parity machine-enforced; the lazy agent path never throws (falls back to the original, warned once). Load when refining / compiling / optimizing a prompt, wiring a refinement store, or exposing a prompt-refine route. Skip: registry ops — `@warlock.js/ai/manage-prompts/SKILL.md`; the builder itself — `@warlock.js/ai/write-system-prompt/SKILL.md`.
|
|
78
|
+
|
|
75
79
|
### [`run-ai-agent/`](./run-ai-agent/SKILL.md)
|
|
76
80
|
|
|
77
81
|
Build agents with ai.agent({...}) — the single-LLM-turn primitive. Covers execute / stream, attachments, structured output, placeholders, events, AgentResult envelope, streamingToolGuard, the judge-safe preset (ai.agent.judge / judge: true — lenient JSON parse + repair + never-throw for Nova-class LLM-as-judge graders), and ai.spawnSubAgent({...}) (a thin one-shot-agent wrapper with a per-task budget — a general primitive, not planner-specific). Load when calling ai.agent(...), reading AgentResult, wiring options.output / attachments / repair, building a resilient LLM-as-judge, streaming, or spawning a one-shot sub-agent.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: manage-prompts
|
|
3
|
-
description: 'Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`; ''register a prompt by name'', ''resolve a prompt by name@version or tag'', ''pin a production tag to a prompt version'', ''diff two prompt versions'', ''export / import the prompt registry'', ''validate a prompt for missing placeholders'', ''merge a registered prompt into another''; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.'
|
|
3
|
+
description: 'Unified prompt registry — ai.prompts: one process-wide store of named, versioned systemPrompt(...) builders keyed by name@version. Register by giving a prompt a meta.name (auto-registers), resolve by get(name) / resolve(name, versionOrTag, placeholders) / the inline name@selector form, bulk-register with define(name, versions), pin tags with tag(name, tag, version), compare with diff(name, from, to), round-trip with export() / import(snapshot), and quality-check with a unified validate(target, options) (deterministic missing-placeholder check + optional Nova-safe LLM-as-judge with verdict caching). Compose registered prompts into new ones with systemPrompt().merge(name, { fromVersion }) — provenance recorded in meta.composedFrom. ai.prompt is now a thin FACADE over ai.prompts (BREAKING vs the old standalone registry). Triggers: `ai.prompts`, `ai.prompt`, `PromptsManagerContract`, `PromptsManagerEntry`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PromptsValidateOptions`, `PromptValidationResult`, `PromptValidateTarget`, `PromptTemplateVersion`, `PromptDiff`, `ExportedRegistry`, `defaultPromptsManager`, `prompts()`, `promptKey`, `meta`, `name`, `version`, `composedFrom`, `fromVersion`, `register`, `create`, `get`, `has`, `list`, `versions`, `resolve`, `define`, `tag`, `validate`, `diff`, `export`, `import`, `merge`, `judge`, `judgeCache`, `criteria`; ''register a prompt by name'', ''resolve a prompt by name@version or tag'', ''pin a production tag to a prompt version'', ''diff two prompt versions'', ''export / import the prompt registry'', ''validate a prompt for missing placeholders'', ''validate a prompt against my own criteria / rules'', ''merge a registered prompt into another''; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing a single prompt from persona + instruction blocks (the builder itself) — `@warlock.js/ai/write-system-prompt/SKILL.md`; runtime loadable skill bodies — `@warlock.js/ai/use-runtime-skills/SKILL.md`; eval scoring of agent outputs — `@warlock.js/ai/eval-datasets-and-ci/SKILL.md`; competing libs `langfuse` (direct), `promptfoo`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `ai.prompts` — the unified prompt registry
|
|
@@ -102,6 +102,11 @@ const report = await ai.prompts.validate("support", {
|
|
|
102
102
|
placeholders: { product: "Warlock" }, // values you intend to supply
|
|
103
103
|
declare: ["language"], // extra keys to treat as known
|
|
104
104
|
judge: judgeModel, // optional — turns on the LLM-as-judge pass
|
|
105
|
+
criteria: [ // optional — YOUR rules, replaces the built-in rubric
|
|
106
|
+
"Addresses the user by {{name}}",
|
|
107
|
+
"Never gives medical advice",
|
|
108
|
+
"Stays under 200 words",
|
|
109
|
+
],
|
|
105
110
|
});
|
|
106
111
|
|
|
107
112
|
report.ok; // true iff no required placeholder is missing (DETERMINISTIC verdict alone)
|
|
@@ -112,6 +117,7 @@ report.issues; // advisory judge reasons / a degrade note — present only whe
|
|
|
112
117
|
|
|
113
118
|
- **Always** runs the deterministic check: every `{{key}}` with no inline default that is neither supplied (`placeholders`), declared (`declare`), nor in the prompt's `meta.required` lands in `missing`; `ok` is `true` iff `missing` is empty.
|
|
114
119
|
- **`judge`** adds a **Nova-safe** LLM-as-judge quality pass — it **never throws** and degrades to an `issues` note (leaving `score` undefined) on failure, so a flaky judge can **never flip `ok`**.
|
|
120
|
+
- **`criteria`** (a string or a list of short rules) grades the prompt against **your own rules** instead of the built-in quality rubric — `score` / `issues` then reflect your criteria (a failed rule is named in `issues`). Only used when `judge` is also set; folded into the `judgeCache` key so different rules re-run. Still advisory — never flips `ok`.
|
|
115
121
|
- **`target`** is a registered name (or `name@selector`), a `SystemPromptContract` instance, or a raw prompt string.
|
|
116
122
|
- **`judgeCache`** (per-call or via the `prompts({ judgeCache })` factory option) memoizes judge verdicts by a content hash of the resolved body + the judge model id — a structural `{ get, set }` subset of `@warlock.js/cache`'s `CacheDriver`, so the cache package stays a strictly **optional** peer.
|
|
117
123
|
|
|
@@ -182,5 +188,6 @@ If you only ever called `ai.prompt({ ... })` and used the returned registry, **n
|
|
|
182
188
|
## See also
|
|
183
189
|
|
|
184
190
|
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — the `systemPrompt()` / `persona()` / `instruction()` builder, `.meta()`, and `merge()` this registry stores and composes
|
|
191
|
+
- [`@warlock.js/ai/refine-prompts/SKILL.md`](@warlock.js/ai/refine-prompts/SKILL.md) — `systemPrompt().refined({ model, criteria, store })`, the prompt compiler; register its `refinePrompt()` output as a next version to `diff` original vs refined
|
|
185
192
|
- [`@warlock.js/ai/eval-datasets-and-ci/SKILL.md`](@warlock.js/ai/eval-datasets-and-ci/SKILL.md) — the eval `judge` scorer `validate()`'s LLM pass reuses
|
|
186
193
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — wiring a resolved prompt into an agent, plus the judge-safe agent preset (`ai.agent.judge`)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refine-prompts
|
|
3
|
+
description: 'Prompt compiler — systemPrompt(...).refined({ model, criteria, store }): humans keep writing human prompt text (dev code or admin-panel textareas); the refined wrapper lazily rewrites it into a model-optimized version via a refiner model on first agent use, pins the result (lockfile posture — recompiled ONLY when the source text, refiner model, criteria, or built-in recipe version change, never silently over time), and serves the pin thereafter. Explicit surfaces: await refined.refine() → the compiled template STRING (placeholders intact — for admin routes, previews, boot warmup, CI; throws PromptRefinementError on failure) and await refined.refinePrompt() → a composable SystemPromptContract with meta.refinedFrom / meta.refinerModel provenance (register it as a next version to unlock ai.prompts.diff review). Placeholder parity is machine-enforced (the exact {{placeholder}} set must survive or the rewrite is rejected after one repair re-ask); the lazy agent path NEVER throws — on refiner failure it warns once and serves the original. store is a structural { get, set } (any @warlock.js/cache CacheDriver); omitted ⇒ the pin lives on the instance for the process lifetime. Triggers: `refined`, `refine`, `refinePrompt`, `materialize`, `RefinedSystemPromptContract`, `RefinedSystemPromptOptions`, `RefinedPromptStoreLike`, `PromptRefineOptions`, `PromptRefinementError`, `refinedFrom`, `refinerModel`, `fresh`, `prompt-refiner`, ''refine a prompt'', ''compile a prompt'', ''optimize a system prompt'', ''rewrite my prompt to be AI-friendly'', ''admin-written prompts'', ''prompt refinement store''; typical import `import { ai } from "@warlock.js/ai"`. Skip: registry operations (register / resolve / tag / diff / validate) — `@warlock.js/ai/manage-prompts/SKILL.md`; composing prompts from persona + instruction blocks — `@warlock.js/ai/write-system-prompt/SKILL.md`; grading a prompt against rules without rewriting it — validate({ criteria }) in `@warlock.js/ai/manage-prompts/SKILL.md`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `systemPrompt().refined()` — the prompt compiler
|
|
7
|
+
|
|
8
|
+
Humans write prompts as human text; models perform better on structured, model-tuned phrasing. `.refined({ model, criteria, store })` turns any `SystemPrompt` into a **lazily-compiled artifact**: the first agent use rewrites the raw source template through the refiner `model`, pins the result, and every later use serves the pin. The human text stays the editing surface forever — the refined text is a derived artifact, like a lockfile.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { ai } from "@warlock.js/ai";
|
|
12
|
+
|
|
13
|
+
const support = ai
|
|
14
|
+
.systemPrompt(
|
|
15
|
+
[ai.persona("You are a friendly assistant."), ai.instruction("Help {{name}} with orders.")],
|
|
16
|
+
{ name: "support" },
|
|
17
|
+
)
|
|
18
|
+
.refined({ model: refinerModel, store: myCacheDriver });
|
|
19
|
+
|
|
20
|
+
// Lazy: compiles on the first run, serves the pin afterwards.
|
|
21
|
+
const agent = ai.agent({ model, systemPrompt: support });
|
|
22
|
+
|
|
23
|
+
// Explicit: compile now — admin routes, previews, boot warmup, CI.
|
|
24
|
+
const text = await support.refine(); // the compiled template STRING
|
|
25
|
+
const prompt = await support.refinePrompt(); // a composable SystemPromptContract
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## The four trust rules
|
|
29
|
+
|
|
30
|
+
1. **Lockfile posture.** The pin key hashes the recipe version + refiner model + `criteria` + source template — any input change compiles fresh; an unchanged input NEVER recompiles (no TTL, no silent drift). `store` is a **store, not a cache**.
|
|
31
|
+
2. **Prose, never contract.** The exact `{{placeholder}}` set (name **and** `|default`) must survive the rewrite verbatim — checked mechanically; a parity break gets ONE repair re-ask, then the rewrite is rejected. The compiled text is still a **template**: placeholders resolve per call as usual.
|
|
32
|
+
3. **Advisory with fallback.** The lazy agent path never throws: a refiner failure warns once (`[warlock-ai] …`) and serves the ORIGINAL prompt — the human text is always a valid prompt. After **3** failed attempts the lazy path stops retrying for the instance lifetime (no per-run refiner latency from a broken key/provider); the explicit `refine()` / `refinePrompt()` stay live — they **throw** `PromptRefinementError` (`error.reason`: `"model"` / `"parity"` / `"empty"`) and a later success re-arms the pin for everyone.
|
|
33
|
+
4. **Reviewable.** `refine()` exposes the compiled text; `refinePrompt()` makes it a first-class prompt with provenance.
|
|
34
|
+
|
|
35
|
+
## `refine(options?)` — the explicit string surface
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const text = await support.refine(); // store-first; pins on first compile
|
|
39
|
+
const another = await support.refine({ fresh: true }); // skip the pin, new take, re-pins
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Expose it via a route for an admin **preview / approve** flow — the admin sees original vs refined, and the call itself warms the pin so the next agent run pays nothing. Also the boot-warmup / CI-compile surface.
|
|
43
|
+
|
|
44
|
+
## `refinePrompt(options?)` — the composable surface
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const compiled = await support.refinePrompt();
|
|
48
|
+
|
|
49
|
+
compiled.blocks; // one instruction block = the refined template
|
|
50
|
+
compiled.meta()?.refinedFrom; // "support@1" (or "anonymous")
|
|
51
|
+
compiled.meta()?.refinerModel; // "anthropic:claude-sonnet-4-5"
|
|
52
|
+
compiled.meta()?.required; // carried from the source — contract preserved
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
It never auto-registers (no `name` — registry versions stay human-intentional). Register it deliberately to unlock the review flow:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
compiled.meta({ name: "support" }); // registers as support@<next>
|
|
59
|
+
ai.prompts.diff("support", "1", "2"); // original vs refined, block by block
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Options
|
|
63
|
+
|
|
64
|
+
- **`model`** (required) — the refiner `ModelContract`. The call runs as a one-shot `"prompt-refiner"` agent, so usage/cost surface through the standard report/observer machinery.
|
|
65
|
+
- **`criteria`** — a string or list of rules the rewrite MUST satisfy, on top of the built-in recipe. Same word and shape as `validate({ criteria })`: *validate grades against criteria; refined rewrites against them*. Folded into the pin key — new rules compile fresh.
|
|
66
|
+
- **`store`** — structural `{ get, set }` (`RefinedPromptStoreLike`; any `@warlock.js/cache` `CacheDriver` satisfies it — the cache package stays an optional peer). Share a redis/pg-backed driver so ONE process pays each compilation and the fleet reads the pin. Omitted ⇒ the pin lives on the wrapper instance for the process lifetime. A pinned value that fails the parity check (corrupt / tampered store) is treated as a miss and recompiled.
|
|
67
|
+
|
|
68
|
+
## What compiles where — the lazy boundary
|
|
69
|
+
|
|
70
|
+
The lazy compile hook rides the **agent path** (`ai.agent` execute/stream, and everything built on it — supervisors' member agents, planner steps, eval, `spawnSubAgent`, `serve`). Prompts resolved **synchronously at factory time** — `ai.planner({ systemPrompt })` / `ai.router({ systemPrompt })` prefixes, a supervisor's own `systemPrompt` / `goal`, and `ai.prompts.resolve()` — use the ORIGINAL text unless you pre-warm:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
await refined.refine(); // warm the pin at boot…
|
|
74
|
+
const planner = ai.planner({ systemPrompt: refined, ... }); // …then factories see it? NO —
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Factory-time resolution reads whatever is pinned **at that moment** — so warm BEFORE constructing the factory, or pass `await refined.refinePrompt()` instead (an already-compiled plain prompt).
|
|
78
|
+
|
|
79
|
+
## Chaining and identity
|
|
80
|
+
|
|
81
|
+
- `refined.meta()` reads the SOURCE meta — agent reports stamp the source `name@version`, so observability groups by the prompt you authored.
|
|
82
|
+
- `.persona()` / `.instruction()` / `.merge()` / `.meta({...})` derive a NEW source and re-wrap it with the same refinement options — editing a compiled prompt invalidates its pin naturally (new source ⇒ new key).
|
|
83
|
+
- `refined.source` is always the original builder; `refined.resolve(placeholders)` serves the compiled text once pinned, the original before.
|
|
84
|
+
- **Register the source or the `refinePrompt()` output — not the wrapper itself.** The wrapper's `blocks` flip from source to compiled text on materialization, so `ai.prompts.register(wrapper)` would fingerprint whatever is pinned at call time (and a re-register after the flip throws on the content mismatch).
|
|
85
|
+
- `validate()` on the wrapper validates what it currently serves — pair `refined` with `validate({ criteria, judge })` to lint the compiled text, and with `agent.eval` (original vs refined on a dataset) to PROVE the rewrite helps before trusting it.
|
|
86
|
+
|
|
87
|
+
## See also
|
|
88
|
+
|
|
89
|
+
- [`@warlock.js/ai/manage-prompts/SKILL.md`](@warlock.js/ai/manage-prompts/SKILL.md) — the registry (`name@version`, tags, `diff`, `validate({ criteria })`) the review flow rides on
|
|
90
|
+
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — the `systemPrompt()` builder `.refined()` extends
|
|
91
|
+
- [`@warlock.js/ai/eval-datasets-and-ci/SKILL.md`](@warlock.js/ai/eval-datasets-and-ci/SKILL.md) — measure original vs refined behaviour on a dataset
|
|
@@ -167,5 +167,6 @@ Three distinct prompts, one common foundation. Base is immutable — safe to sha
|
|
|
167
167
|
## See also
|
|
168
168
|
|
|
169
169
|
- [`@warlock.js/ai/manage-prompts/SKILL.md`](@warlock.js/ai/manage-prompts/SKILL.md) — the `ai.prompts` registry these named prompts auto-register into (resolve / version / tag / diff / export / validate)
|
|
170
|
+
- [`@warlock.js/ai/refine-prompts/SKILL.md`](@warlock.js/ai/refine-prompts/SKILL.md) — `.refined({ model, criteria, store })`, the prompt compiler: lazily rewrite this builder into a model-optimized version, pinned like a lockfile
|
|
170
171
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `systemPrompt` on factory + per-call override
|
|
171
172
|
- [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — per-step agent references inherit their own system prompt
|