Skip to content

feat(prompts): prompt builder — compose N prompts into one based on runtime logic #71

@zrosenbauer

Description

@zrosenbauer

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 .prompt file needed
  • Client-level APIprompts.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 codeif statements, 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 ctxctx.client provides 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 together

Open 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 composites namespace?
  • 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 / PromptModulepackages/prompts/src/prompt.ts
  • createPromptRegistrypackages/prompts/src/registry.ts
  • Codegen pipeline — packages/cli/src/lib/prompts/codegen.ts
  • Generated client usage — examples/prompts-basic/src/index.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions