Crux
GuidesPrompts

Prompts

Define typed, composable prompts that run with any SDK adapter.

Prompt strings, input shapes, output schemas, and model settings tend to scatter across call sites as an application grows. prompt() keeps that call contract in one typed place.

A Prompt describes what the model should see, what input the call accepts, what output it should return, and which reusable capabilities it needs. It does not call a model by itself. Adapters execute the same definition through Vercel AI SDK, OpenAI, Google GenAI, or Anthropic.

Define Your First Prompt

Start with ordinary strings. Markdown-oriented md templates are optional.

support-prompt.ts
import { prompt } from "@use-crux/core";
import { z } from "zod";

export const answerSupport = prompt({
  id: "answer-support",
  input: z.object({
    question: z.string(),
  }),
  system: "You are a concise product support assistant.",
  prompt: ({ input }) => input.question,
  settings: { temperature: 0.2 },
});

Run that definition with the adapter for the SDK you already use:

run.ts
import { generate } from "@use-crux/ai";
import { openai } from "@ai-sdk/openai";

const result = await generate(answerSupport, {
  model: openai("gpt-4o-mini"),
  input: {
    question: "How do I rotate an API key?",
  },
});

console.log(result.text);

See Prompt execution for streaming and every supported adapter.

The Mental Model

A Prompt owns one model-call contract:

prompt({
  id: "answer-support",
  use: [brand, docs, memory],
  input: InputSchema,
  output: OutputSchema,
  system: "Who the assistant is.",
  prompt: ({ input }) => `What the user asked: ${input.question}`,
});
FieldWhat it owns
idStable identity for logs, evals, cache keys, Catalog, and Devtools
inputRuntime data the prompt accepts
outputStructured output schema; omit it for text generation
systemRole, policy, and high-level instructions
promptThe user or task message for a single-turn call
messagesFull message history for chat, few-shot examples, or role-specific turns
useReusable contexts and capabilities resolved into the call
toolsTools owned by this prompt
settingsSDK-agnostic defaults such as temperature and maximum tokens
testsInline Eval cases for CI and local regression checks

The normal composition path is use. Put a capability in that array and Crux resolves what it contributes: instructions, tools, constraints, metadata, or lifecycle behavior.

Choose Text or Structured Output

Omit output when the model should return text:

const rewrite = prompt({
  id: "rewrite",
  input: z.object({
    text: z.string(),
    tone: z.enum(["clear", "friendly", "direct"]),
  }),
  system: "Rewrite without changing the meaning.",
  prompt: ({ input }) => `Tone: ${input.tone}\n\n${input.text}`,
});

Add a Zod output schema when downstream code needs validated data:

const classifyTicket = prompt({
  id: "classify-ticket",
  input: z.object({
    subject: z.string(),
    body: z.string(),
  }),
  output: z.object({
    category: z.enum(["billing", "auth", "bug", "other"]),
    urgency: z.enum(["low", "normal", "high"]),
    summary: z.string(),
  }),
  system: "Classify support tickets for routing.",
  prompt: ({ input }) => `${input.subject}\n\n${input.body}`,
});

Crux compiles your authored schema to the provider's supported wire shape, then validates the response against the schema you wrote. See Structured Output for provider lowering, optional fields, and result types.

Compose Capabilities With use

Contexts, memory, retrieval, grounding, skills, and other contributors use the same composition surface:

composition.ts
import { context, history, prompt } from "@use-crux/core";
import { memory, facts } from "@use-crux/core/memory";
import { grounding } from "@use-crux/core/retrieval";

const policy = context({
  id: "support-policy",
  system: "Do not invent product behavior.",
});

const userMemory = memory({
  id: "user-memory",
  store,
  namespace: ({ input }) => `user:${input.userId}`,
  blocks: [facts({ id: "facts", embed: dense })],
});

const docsGrounding = grounding({
  id: "docs-grounding",
  retriever: docs,
  inject: "both",
  citations: true,
});

const support = prompt({
  id: "support",
  use: [history.recent({ messages: 8 }), policy, userMemory, docsGrounding],
  input: z.object({
    userId: z.string(),
    question: z.string(),
  }),
  system: "Answer the support question.",
  prompt: ({ input }) => input.question,
});

The prompt names its dependencies. Each primitive owns what it contributes.

Package Reusable Wiring in a Context

If several prompts should share the same policy and capabilities, package that wiring in a Context:

const supportKnowledge = context({
  id: "support-knowledge",
  use: [docsGrounding, userMemory],
  system: "Use product documentation before user memory.",
});

const reply = prompt({
  id: "reply",
  use: [supportKnowledge],
  input: z.object({
    userId: z.string(),
    question: z.string(),
  }),
  system: "You are a support assistant.",
  prompt: ({ input }) => input.question,
});

Use direct prompt({ use: [...] }) for one-off wiring. Use context({ use: [...] }) when the combination is a reusable product capability.

Choose system + prompt or messages

Use system and prompt for the common single-turn shape. Use messages when the message list itself is the source of truth: chat history, few-shot examples, or role-specific turns.

const chatReply = prompt({
  id: "chat-reply",
  input: z.object({
    userMessage: z.string(),
  }),
  messages: ({ input }) => [
    { role: "system", content: "Answer as a careful assistant." },
    { role: "user", content: "Answer in one sentence." },
    { role: "assistant", content: "I will keep responses concise." },
    { role: "user", content: input.userMessage },
  ],
});

Do not combine messages with system or prompt.

Inspect Before You Run

Use .resolve() for provider-neutral prompt data or preview() for whole-request fit and prospective adaptations. Neither calls a model.

const inspection = await preview(support, {
  model,
  input: {
    userId: "user_123",
    question: "How do I configure SSO?",
  },
  inputBudget: { max: 4000 },
});

console.log(inspection.status);
console.log(inspection.adaptations);
console.log(inspection.diagnostics);

For Markdown-aware source previews and captured runtime evidence, see PromptText.

What to Reach For

NeedUse
One model call with typed input/outputprompt()
Readable multiline prompt compositionmd
Reusable instructions or input fieldscontext() in use
Runtime conditional instructionscontext({ when }), when(), or match()
Durable user or session recallmemory() in use
Retrieved evidence and citationsretriever() or grounding() in use
Markdown instructions loaded on demandskill() in use
Prompt regression testingtests or evaluate()
Provider-specific prompt tweaksadapt

Pick a Topic

On this page