Recipes API (Recipe Handles)
Recipes are the front door to llm-core. Everything is a recipe handle: some recipes are leaf steps, some are composite, but the public surface is the same.
> **Recipes are the public story.** Packs/flows are internal composition tools; most users never need them.
Related:
The Recipe Catalog
llm-core exports a catalog of recipe factories. You access them by name.
import { recipes } from "@geekist/llm-core/recipes";
const agent = recipes.agent();
const rag = recipes.rag();Available recipes:
agent: ReAct agent with tools, planning, and memory.rag: Retrieval-augmented generation.chat.simple: Basic chat with system prompt.chat.rag: RAG specialized for chat history.hitl: Human-in-the-loop gate.ingest: Document ingestion pipeline.eval: Generation evaluation.loop: Iteration control.
The agent recipe aligns with the agent loop contract in @geekist/llm-core/interaction, which defines the event semantics (interaction.item.*, interaction.subagent.*) and deterministic state snapshots used by UI integrations. The contract is adapter-agnostic, and apps do not need to wire packs manually to use it. The agent recipe is the reference implementation of this contract; you can still build your own recipes on the same types if you need a custom loop.
Recipe handles (the public surface)
Every recipe exposes the same handle methods:
configure(config)— recipe-specific behavior (prompts, retrieval knobs, tool options).defaults(defaults)— wiring and infra (adapters, plugins, retry defaults).use(otherRecipe)— composition (pluggable sub-recipes).explain()— returns the step graph (no side effects).build()— returns a reusable runnable.run(input, runtime?)— one-shot execution.
Use defaults({ retryDefaults }) when you want per-recipe retry policy defaults; per-run overrides live in runtime.retry.
import { recipes } from "@geekist/llm-core/recipes";
import type { AgentRecipeConfig } from "@geekist/llm-core/recipes";
// Configure once, reuse across requests.
const agent = recipes["agent"]()
.configure({
planning: { modelInstructions: "Plan before acting." },
tools: { toolChoice: "auto" },
} satisfies AgentRecipeConfig)
.defaults({ adapters: { model, tools, memory } }); // Wire adapters once.
const result = await agent.run({ input: "Help me debug this RAG flow." });Recipe-specific config (no global junk drawer)
Each recipe exports its own config type. configure() only accepts that type. There is no global RecipeConfig bag.
import type { RagRecipeConfig } from "@geekist/llm-core/recipes";
const rag = recipes["rag"]().configure({
retrieval: { topK: 8 },
synthesis: { system: "Cite your sources." },
} satisfies RagRecipeConfig);explain(): see the graph
explain() gives you a stable, inspectable view of the recipe DAG.
type PlanView = { steps: Array<{ id: string }> };
const plan: PlanView = recipes["rag"]().explain();
// Inspect the resolved step graph.
console.log(plan.steps.map((step) => step.id));build(): reusable runnables
build() is optional, but useful when you want to cache a configured recipe.
const runnable = recipes["rag"]()
.configure({ retrieval: { topK: 5 } })
.defaults({ adapters: { model, retriever } }) // Keep wiring separate.
.build();
type RagRunnable = typeof runnable;
const outcome = await(runnable as RagRunnable)({ input: "Explain DSP." });