Components

Components

Everything an agent renders, imported from curb.

ComponentWhat it does
AgentThe root of an agent, and where its providers are set
SystemPromptThe system prompt
SkillsThe list of skills the model may open
SkillOne skill the model may open
GlobalToolsTools every skill has
InstructionBehavioural guidance for the model
TextFactual context
ToolWhat the model can call
FieldOne input field of a Tool, written as a child
ErrorMessageWhat the model reads when a field’s input is invalid
ProcedureAn ordered flow of steps
StepOne step of a Procedure
McpTools from an MCP server
TransferCallToolA ready tool that puts the caller through to a number

<Agent>

The root of the tree. Everything the agent says and can do renders inside it.

PropTypeDefaultWhat it does
llmLLM providerkugelaudio.llm()The model
sttSpeech-to-text providerazure.stt()Speech recognition
ttsText-to-speech providerkugelaudio.tts()The voice
adjustable{ client?: paths, host?: paths }nothingWhat the caller’s page and the host may change during a call. Paths: "tts.voice", "tts.model", "stt.language", "llm" (Mid-call changes)

A changed prop swaps that provider from the next render, also during a call.

import { Agent, SystemPrompt } from "curb";
import { kugelaudio } from "curb/providers";

<Agent llm={kugelaudio.llm()} stt={kugelaudio.stt({ language: "de" })} tts={kugelaudio.tts({ language: "de" })}>
  <SystemPrompt>You answer calls for an insurance company.</SystemPrompt>
</Agent>

<SystemPrompt>

The system prompt. Its children are the prompt: nothing is added when it compiles. A voice call adds Curb’s spoken-reply rules at runtime.

<SystemPrompt>You answer calls for an insurance company. Keep every reply short.</SystemPrompt>

<Skills>

Holds the <Skill> links the model may open. It takes no props.

<Skill>

One link to a skill folder under src/agent/. The model opens it with read_skill.

PropTypeWhat it does
pathstringThe skill’s path: /claims for src/agent/claims/
descriptionstringWhen the model should open it
llm, stt, ttsprovidersReplace the <Agent>’s while this skill is the active one
<Skills>
  <Skill path="/claims" description="Reporting a claim or asking about an open one." />
</Skills>

<GlobalTools>

Tools inside it are available in every skill. It takes no props.

<GlobalTools>
  <TransferCallTool to="+493012345678" description="The caller asks for a person." />
</GlobalTools>

<Instruction>

Behavioural guidance for the model. It takes no props.

<Instruction>Never promise that a claim will be paid.</Instruction>

<Text>

Factual context. A plain string in JSX is factual context too; use <Text> for a clear boundary or to load a file.

PropTypeWhat it does
srcstringA markdown file under context/

Give src or children, never both.

<Text src="deductibles.md" />

<Tool>

What the model can call. A tool that is not rendered is not in the request (State and conditional tools).

Every tool takes these:

PropTypeWhat it does
namestringThe name the model calls
descriptionstringWhen to call it, for the model
iconstringA lucide icon name for the preview. Never reaches the model

Then one of three ways to take input:

FormPropsUse it for
Schemaschema, onSubmit, tryWithMost tools: a Zod object, and typed input in onSubmit
Fields<Field> children, onSubmit, tryWithA few flat fields
No inputonCallA tool that takes nothing

onSubmit and onCall return a ToolResult, { ok, message?, data? }, or a promise of one. With schema or fields, two more props apply, and confirm holds the tool until the caller agrees:

PropTypeWhat it does
successstringThe line after a successful run. Without it, the result’s message
errorstringThe line when a failed result has no message
tryWithobjectWhat the preview’s Try form opens with. Never reaches the model
confirm(input) => stringThe question that holds the tool until the caller says yes. Only with onSubmit (Confirmations)
import { Tool, z } from "curb";

<Tool
  name="open_claim"
  description="Open a claim once the caller has described what happened."
  schema={z.object({ policy: z.string(), what: z.string() })}
  onSubmit={async ({ policy, what }) => {
    const id = await claims.open(policy, what);
    return { ok: true, message: `Claim ${id} is open.` };
  }}
/>

A handler can refuse its input with ToolInputError.

<Field>

One input field of a <Tool>, written as a child. It adds nothing a Zod schema does not; it is shorter for a few flat fields.

PropTypeWhat it does
namestringThe input’s name
type"string", "number", "integer", "boolean", "email"Its type
descriptionstringWhat it is, for the model
requiredbooleanWhether the model must fill it
<Tool name="find_policy" description="Find a policy by its number." onSubmit={findPolicy}>
  <Field name="number" type="string" description="The policy number" required />
</Tool>

<ErrorMessage>

What the model reads when the input of a field is invalid.

PropTypeWhat it does
sourcestringThe field it belongs to

<Procedure>

An ordered flow of <Step>s. The model gets one step at a time and the advanceTool to move on (Step-by-step procedures).

PropTypeDefaultWhat it does
titlestringThe flow’s name
advanceTool{ name, description?, onCall? }complete_procedure_stepThe tool that finishes a step. It takes no input
completedReactNodeRendered once every step is done
onComplete() => voidRuns when the last step is done
initialStepnumber1Where it starts. Steps count from 1
currentStepnumberHolds the step yourself, with onStepChange
onStepChange(step) => voidRuns when the step changes

<Step>

One step of a <Procedure>. Its children render only during that step.

PropTypeWhat it does
titlestringThe step’s name
<Procedure title="Report a claim" completed={<Tool name="submit_claim" description="File the claim." onCall={submit} />}>
  <Step title="Ask what happened">Ask what happened and when.</Step>
  <Step title="Ask for the policy number">Ask for the policy number.</Step>
</Procedure>

<Mcp>

Tools from an MCP server (MCP servers).

PropTypeWhat it does
idstringA server configured in defineAgent({ mcpServers }), also the prefix of its tools (<id>__<tool>)
urlstringDeclares the server here: no mcpServers entry is needed
auth{ kind: "none" }, { kind: "bearer", token }, { kind: "oauth" }Default: a bearer token from MCP_<ID>_TOKEN when it is set, else OAuth, signed in once in the preview
headersobjectHeaders for each request
onlystring[]Keep only these tools

With neither id nor url, it brings in every registered server. In <GlobalTools> the tools reach every skill; in a skill, only that skill.

<TransferCallTool>

A ready tool that puts the caller through to a number and tells the model what happened (Human handoff).

PropTypeDefaultWhat it does
tostringThe number in international format, like +493012345678
namestringtransfer_callThe tool’s name
descriptionstringsays what it doesWhen the model should call it
ringTimeoutSecondsnumber30How long it rings, 10 to 60 seconds

Next steps