-
Notifications
You must be signed in to change notification settings - Fork 0
feat(prompts): prompt builder — compose N prompts into one based on runtime logic #71
Description
Summary
Add a composite prompt concept to @funkai/prompts as a purely runtime TypeScript API on the prompt registry client. No new file format — composites are defined in code when building or using the client.
The consumer defines a composite using the client, and it becomes part of the registry like any other prompt. The composition logic is a builder callback that receives a context object with validated inputs and the base client (all non-composite prompts), and returns an array of rendered strings that get joined automatically.
Current state
Today, each .prompt file generates a single PromptModule with one template and one schema (packages/cli/src/lib/prompts/codegen.ts). The generated registry (index.ts) wires them into a frozen tree accessed via ~prompts:
import { prompts } from '~prompts'
// Each leaf is a single PromptModule — one template, one render
prompts.agents.writer.render({ tone: 'friendly', context: '...' })There's no way to define a prompt that internally composes from multiple sub-prompts based on what the caller provides. If you want conditional sections, you either:
- Cram everything into one massive LiquidJS template with
{% if %}blocks - Use
{% render %}partials (static includes, not input-driven selection) - Manually concatenate multiple
.render()calls in userland
Proposed API
Composites are defined purely in TypeScript on the client — no .prompt file, no codegen:
prompts.composite('agent-system', {
schema,
build: ({ input, client }) => {
const parts = [
client.agents.persona.render({ role: input.role, domain: input.domain }),
];
if (input.tools) {
parts.push(client.agents.toolInstructions.render({ tools: input.tools }));
}
if (input.files) {
parts.push(client.agents.codeContext.render({ files: input.files }));
}
return parts;
},
})ctx shape:
| Property | Description |
|---|---|
ctx.input |
Validated inputs (typed from the schema) |
ctx.client |
The base prompt registry client (without composites — prevents circular references) |
The ctx object is extensible — future additions (e.g. ctx.separator, ctx.metadata) won't break existing signatures.
Key design points
- Pure TypeScript — no new file format, no codegen changes, no
.promptfile needed - Client-level API —
prompts.composite()registers a composite directly on the client - No declarative variable mapping — you call
.render()with plain JS, passing whatever variables you want - Conditional logic is just code —
ifstatements, not YAML config - Conforms to
PromptModule— the composite implements the same interface (name,group,schema,render,validate), so it's usable anywhere a regular prompt is used - Composable — composites can reference other composites (since they conform to
PromptModule) - Base client in ctx —
ctx.clientprovides the registry without composites to avoid circular references
Usage
Once registered, a composite is used like any other prompt:
// Define the composite
prompts.composite('agent-system', {
schema,
build: ({ input, client }) => [
client.agents.persona.render({ role: input.role }),
...(input.tools
? [client.agents.toolInstructions.render({ tools: input.tools })]
: []),
],
})
// Use it — same interface as any prompt
const systemPrompt = prompts.agentSystem.render({
role: 'engineer',
domain: 'TypeScript',
tools: ['read', 'write'],
})
// => Persona section + Tool instructions section joined togetherOpen questions
- Should composites support a custom separator/joiner between rendered sections (default
\n\n)? - Where do composites live in the registry tree — top-level, or nested under a
compositesnamespace? - Should
prompts.composite()return a new client (immutable/chainable) or mutate the existing one? - Can composites reference other composites via
ctx.client, or only base prompts?
Related
createPrompt/PromptModule—packages/prompts/src/prompt.tscreatePromptRegistry—packages/prompts/src/registry.ts- Codegen pipeline —
packages/cli/src/lib/prompts/codegen.ts - Generated client usage —
examples/prompts-basic/src/index.ts