Knobs & Controls
Knobs are model-visible, model-settable reactive state. One hook call creates a value, renders it as a form control the model can see, and registers a tool for the model to change it.
This is the killer feature of the reconciler approach. Without a component model, you'd need to manually:
- Define the state variable
- Add it to the system prompt
- Register a tool to modify it
- Update the prompt when it changes
- Validate the new value
useKnob does all five in one line.
Basic Usage
function Agent() {
const [depth, setDepth] = useKnob("search_depth", 3, {
min: 1,
max: 10,
description: "Number of search results to analyze",
});
return (
<>
<System>Analyze the top {depth} results for each query.</System>
<SearchTool maxResults={depth} />
<Knobs />
<Timeline />
</>
);
}The model sees something like:
### Knobs
- search_depth [range]: 3 (min: 1, max: 10) — Number of search results to analyze
Use set_knob to change.And has access to a set_knob tool to modify it.
Type-Safe Constraints
Constraints are inferred from the value type:
// Number knobs: min, max, step
const [temp] = useKnob("temperature", 0.7, { min: 0, max: 2, step: 0.1 });
// String knobs: maxLength, pattern
const [format] = useKnob("output_format", "markdown", {
maxLength: 20,
pattern: "^(markdown|html|text)$",
});
// Boolean knobs: rendered as toggles
const [verbose] = useKnob("verbose", false, {
description: "Include detailed explanations",
});
// Enum knobs: rendered as selects
const [style] = useKnob("writing_style", "academic", {
options: ["academic", "casual", "technical", "creative"],
description: "Output writing style",
});Semantic Types
Agentick infers a semantic type from the value and constraints:
| Value + Constraints | Semantic Type |
|---|---|
boolean | [toggle] |
number with min+max | [range] |
number without range | [number] |
Any type with options | [select] |
string | [text] |
Groups
Organize knobs into named groups:
const [temp] = useKnob("temperature", 0.7, {
group: "Model Settings",
min: 0,
max: 2,
});
const [maxTokens] = useKnob("max_tokens", 1000, {
group: "Model Settings",
min: 100,
max: 4000,
});
const [style] = useKnob("style", "helpful", {
group: "Behavior",
options: ["helpful", "concise", "creative"],
});Groups render with ### GroupName headers in the model context.
Rendering Modes
Default
<Knobs /> renders a section and registers the set_knob tool:
<Knobs />Render Prop
Custom section rendering, tool auto-registered:
<Knobs>
{(groups) => (
<Section id="my-knobs">
{groups.map((g) => `## ${g.name}\n${g.knobs.map((k) => k.display).join("\n")}`).join("\n")}
</Section>
)}
</Knobs>Provider
Full control — provider registers the tool, you handle rendering:
<Knobs.Provider>
<MyCustomKnobUI />
</Knobs.Provider>;
// In MyCustomKnobUI:
function MyCustomKnobUI() {
const { knobs, groups, get } = useKnobsContext();
// Full custom rendering
}Validation
The set_knob tool handler validates automatically:
- Type check (number, string, boolean)
- Options check (if
optionsdefined) - Range check (min/max for numbers)
- Length/pattern check (for strings)
- Custom
validatefunction (if provided)
const [priority] = useKnob("priority", 5, {
min: 1,
max: 10,
validate: (v) => (v % 1 === 0 ? true : "Must be a whole number"),
});Config-Level Knobs
For declaring knobs outside components (e.g., in createAgent config):
import { knob } from "agentick";
const agent = createAgent({
knobs: {
temperature: knob(0.7, { min: 0, max: 2 }),
style: knob("helpful", { options: ["helpful", "concise"] }),
},
});knob() returns a KnobDescriptor — a branded object detected by isKnob() at runtime.
Momentary Knobs
Momentary knobs auto-reset to their default value at the end of each execution. The model sets the knob to expand context, acts on it, and the knob resets automatically — reclaiming tokens.
import { knob, useKnob, Knobs } from "agentick";
// Config-level
const planningWorkflow = knob.momentary(false, {
description: "Account planning workflow",
});
// Or inline
function Agent() {
const [showPlanning] = useKnob("planning", false, {
description: "Account planning workflow",
momentary: true,
});
return (
<>
<Knobs />
{showPlanning && (
<Section id="planning" audience="model">
...
</Section>
)}
<Timeline />
</>
);
}The model sees planning [momentary toggle]: false — Account planning workflow (resets after use). The reset happens after the tick loop but before the snapshot is persisted, so restored sessions always start clean.
Conditional Context (Accordion Pattern)
Like accordions in a UI — the model sees section headers and expands what it needs. Combine useKnob + momentary + conditional rendering:
Toggle
function Agent() {
const [showDocs] = useKnob("show_docs", false, {
description: "Expand the full API reference",
momentary: true,
});
return (
<>
<System>Set show_docs when you need the full reference.</System>
<Section id="api-ref" audience="model">
{showDocs ? fullApiDocs : "API Reference (set show_docs to expand)"}
</Section>
<Knobs />
<Timeline />
</>
);
}Model sees the collapsed placeholder → sets the knob → reads the content → answers. After the execution, momentary resets the knob and the section collapses. Tokens reclaimed automatically.
Mutual Exclusion
Only one section open at a time:
const sections = ["api", "billing", "troubleshooting"] as const;
const content: Record<string, string> = {
/* ... */
};
function SupportAgent() {
const [active] = useKnob("section", "none", {
options: ["none", ...sections],
description: "Which documentation section to expand",
momentary: true,
});
return (
<>
<System>Expand a section when you need it.</System>
{sections.map((s) => (
<Section key={s} id={s} audience="model">
{active === s ? content[s] : `${s} (expand to read)`}
</Section>
))}
<Knobs />
<Timeline />
</>
);
}The model sees all headers at once, picks the one it needs. Momentary reset means every turn starts with a clean table of contents.
Detail Levels
Progressive disclosure with a number knob:
const [detail] = useKnob("detail_level", 1, {
min: 1,
max: 3,
description: "1=headers, 2=summaries, 3=full content",
momentary: true,
});
return (
<Section id="context" audience="model">
{detail >= 1 && headers}
{detail >= 2 && summaries}
{detail >= 3 && fullContent}
</Section>
);The pattern works with any knob type — booleans for toggles, enums for mutual exclusion, numbers for granularity. The model controls its own attention window.