Skip to content

Custom Intents

Custom intents are how you introduce your own intent names into the protocol.

Think of them as domain-level events that you define yourself.

Instead of forcing the model to express a behavior through free text, you declare a named contract and let the model emit that contract directly.


You define custom intents as top-level intent declarations, then attach them to an agent (or helper) through intent: config.

intent Planning {
description: "Emit a structured execution plan before taking action"
fields {
step: string
confidence: number
rationale: string
}
}
intent Dialogue {
description: "Emit the user-facing dialogue decision"
fields {
tone: string
summary: string
}
}
agent Main {
default config {
model: gemini("gemini-2.5-flash")
prompt: "You are a planning assistant"
}
intent: Planning + Dialogue
}

You can also inline custom intents directly inside intent::

agent Main {
default config {
model: gemini("gemini-2.5-flash")
prompt: "You are a planning assistant"
}
intent: {
Planning {
description: "Emit plan"
fields {
step: string
}
}
}
}

The runtime callback signature pattern stays the same:

  • TypeScript callback receives (name, value, agentName).
  • Python class method receives (value, agent_name), where method name maps to intent name.
agent.onIntent((name, value, agentName) => {
if (name === "Planning") {
console.log("step:", value.step)
console.log("confidence:", value.confidence)
}
if (name === "Dialogue") {
console.log("tone:", value.tone)
console.log("summary:", value.summary)
}
})

Python note: method lookup tries exact intent name first, then a sanitized lowercase form.


For custom intents, the intent name comes from the callback’s name parameter.

The payload is the declared fields object directly.

So if the emitted intent is Planning, payload looks like:

// name === "Planning"
{
step: "Collect user constraints",
confidence: 0.92,
rationale: "Need constraints before proposing options"
}

And for Dialogue:

// name === "Dialogue"
{
tone: "calm",
summary: "I will ask two questions before making a recommendation"
}

If a custom intent is emitted during streaming, onIntentPartial / on_intent_partial receives the same partial envelope style used by other structured intents.

At completion, onIntent / on_intent receives the final fields object.