Agentic Mode
Reference for building agentic projects. Phases drive a standardised but dynamic conversation — each phase has its own goal, tool allowlist, optional buttons, and context injection. The agent moves between phases automatically when exit conditions fire, or stays put if the user goes off-script. State is the source of truth; the model sees a curated snapshot every turn.
Activation
A project runs in agentic mode when its framework field is set to "agentic". You can also enable it via behaviourConfig.agentHarness.enabled for legacy projects whose framework was set to something else but still need the harness.
{
"framework": "agentic",
"behaviourConfig": {
"agentHarness": {
"implementation": "default-v1",
"phases": [ /* ... */ ],
"uiBuiltins": { /* ... */ }
}
}
}Phases
A phase is a labelled mode the agent operates in. The active phase is stored in session state (default key _ch_conversation_phase) and determines what instructions the model gets, which tools it can call, and what UI it can emit. Each phase has these fields:
Stable identifier. Stored in state to mark which phase is active.
Human-readable label shown in the dashboard. Not used at runtime.
Phase-specific system-prompt addendum. Appended after persona + project instructions for any turn the phase is active.
Set to true on the entry phase. If multiple are flagged, the first wins. If none, the first phase in the array is used.
Tools the model may call this phase — intersected with project + uiBuiltins. Omit to inherit; empty array = no tools.
Declarative buttons emitted on phase enter. Each tap merges sets into state immediately, before phase resolution.
KB query run once on phase enter. Top-K results persist to <phaseId>_context for the model to reference.
Equality filters over KB chunk metadata applied to the kbQuery.
Max KB hits to inject. Default 5.
Override snapshot keys for this phase only.
Expression evaluated after each LLM turn. When true, phase advances to nextPhase same turn.
Phase id to advance to when exitWhen fires. If absent, the model must use set_state to leave.
Phase advance
A phase advances when one of three signals fires (in priority order):
1. Button-set
When the user taps a phase button declared on phase.buttons[].sets, the harness merges those keys into state before evaluating the phase. So a tap that sets the gating variable can advance the phase on the same turn.
2. exitWhen
After each LLM turn, the harness evaluates the phase's exitWhen expression against current state. If it's true, the phase changes to nextPhase and the new phase's enter side-effects (KB injection, buttons trace) fire same turn.
3. Model-driven via set_state
The model can call set_state({ variables: { _ch_conversation_phase: "next" } }) directly during the loop. The harness picks up the change at end of turn and fires enter side-effects for the new phase. Useful when the right transition can't be expressed as a simple variable check.
exitWhen DSL
A small expression language for phase transitions. Variables reference state.variables. Strings must be double-quoted.
Operators
- var = "value" — equals
- var != "value" — not equals
- var IS NOT NULL — set to a non-empty value
- var IS NULL — unset / empty
- a AND b — both
- a OR b — either
Examples
- check_type IS NOT NULL
- user_decision = "book"
- quote_total IS NOT NULL AND user_email IS NOT NULL
- requires_review = "true" OR safety_flag = "true"
Note: parentheses are not currently supported. Splitting on AND / OR is naïve, so quoted literals containing those substrings can break parsing — keep values short and simple.
Snapshot keys
Each turn the harness builds a session snapshot — the subset of state variables surfaced to the model in the user-turn prompt. The snapshot is what makes the model coherent across interruptions and off-script questions: it's how the agent remembers what was already collected.
Resolution priority (first match wins):
- phase.contextInjection.snapshotKeys — per-phase override
- behaviourConfig.agentHarness.snapshotKeys — project-wide override
- Auto-derived from project.variables[] in declaration order, filtered to remove system / internal keys, capped at 32 entries
Empty values are skipped silently. System keys (anything starting with _ or vf_, plus conversation_memory, last_event, last_response, sessions, user_id, is_after_hours, timestamp) are never auto-surfaced.
Built-in tools
Agentic mode ships with a built-in tool catalog the model can call. Some are always available; others are opt-in via behaviourConfig.agentHarness.uiBuiltins. See the Tools page for full schemas.
Always available
- set_state
- kb_lookup
- kb_search (if KB wired)
- show_buttons
- request_human
- handoff (if playbooks defined)
Opt-in via uiBuiltins
- show_badge
- show_tip
- show_summary
- show_action_steps
- show_contact_card
All default to disabled.
Tool filtering layers
Tools are filtered through three layers before reaching the model on a given turn (most permissive to most restrictive):
Top-level config
Every field under behaviourConfig.agentHarness:
behaviourConfig: {
agentHarness: {
enabled?: boolean;
implementation?: 'default-v1' | 'checkhero-v1';
// Filters (each is intersected with the rest):
allowedToolIds?: string[]; // project tools the harness may use
allowedPlaybookIds?: string[]; // playbooks reachable via handoff
maxHandoffs?: number; // default 2
// Snapshot:
snapshotKeys?: string[]; // override auto-derivation
// UI builtins (all default false):
uiBuiltins?: {
showBadge?: boolean;
showTip?: boolean;
showSummary?: boolean;
showActionSteps?: boolean;
showContactCard?: boolean;
};
// Phase machinery:
phases?: PhaseConfig[];
phaseStateVar?: string; // default '_ch_conversation_phase'
}
}Worked example
A 3-phase support flow: greet → triage → confirm.
{
"framework": "agentic",
"behaviourConfig": {
"agentHarness": {
"uiBuiltins": { "showBadge": true, "showTip": true },
"phases": [
{
"id": "greet",
"name": "Greet",
"instructions": "Greet the user and ask what they need help with.",
"startPhase": true,
"allowedToolIds": ["set_state", "show_buttons"],
"buttons": [
{ "label": "Account", "sets": { "topic": "account" } },
{ "label": "Billing", "sets": { "topic": "billing" } }
],
"exitWhen": "topic IS NOT NULL",
"nextPhase": "triage"
},
{
"id": "triage",
"name": "Triage",
"instructions": "Gather the user's name + email + a one-line summary.",
"allowedToolIds": ["set_state", "kb_search", "show_tip", "request_human"],
"contextInjection": {
"kbQuery": "{topic} support process",
"topK": 3
},
"exitWhen": "user_email IS NOT NULL AND issue_summary IS NOT NULL",
"nextPhase": "confirm"
},
{
"id": "confirm",
"name": "Confirm",
"instructions": "Show the captured info and ask for confirmation.",
"allowedToolIds": ["set_state", "show_summary", "show_buttons"]
}
]
}
}
}Next
- Tools & Integrations — full schema for every harness builtin and project tool type.
- Conversations API — wire format for interact requests + SSE event reference.
- Runtime Modes — comparison with conversation-flow mode.