Agents¶
The agentloop statement executes and manages a session-long AI agent
inside your scripted bot, effectively handing off control flow from
Bubblescript to the LLM.
Unlike one-off LLM.complete calls, the agent keeps running
for the rest of the conversation: user messages are forwarded into the
loop, and the model can call declared Bubblescript tasks and dialogs
as tools without a hand-written prompt dialog.
Minimal example¶
dialog main do
_tools = [
%{type: "task", task: "get_weather"},
%{type: "dialog", dialog: "completed"}
]
agentloop(
LLM.prompt(
id: "weather_agent",
text: "system: You are an assistant specializing in the weather. Use [Get weather](urn:tool_call:get_weather) to fetch a forecast, and [Completed](urn:tool_call:completed) when the conversation is over.\nassistant: Greet the user to start.",
tools: _tools
)
)
say "Very well, glad to be of service."
close
end
declare task get_weather,
description: "Get weather forecast information for a city",
in: %{type: "object", properties: %{city: %{type: "string", description: "The city for the weather report"}}},
out: %{type: "string", description: "The forecast"}
task get_weather do
_city = _args.city
return random(["sun", "rain", "cloud", "tornado"])
end
declare dialog completed,
description: "When the conversation is over"
dialog completed do
# continues the conversation after the `agentloop` statement
continue
end
For background on prompts and tool declarations, see LLM / ChatGPT support and LLM Tool calling.
Starting an agent¶
Use agentloop(prompt) to start the platform agent loop.
The argument must be a real LLM prompt — either LLM.prompt(...) or a
prompt constant such as @prompts.weather_agent from prompts.yaml or an
individual prompt file. A plain string is not accepted; the runtime stops with
agentloop requires a prompt.
The conversation so far is included automatically. Before the prompt is
prepared the runtime appends [[ full_transcript ]], so the turns that came
before the agentloop statement reach the agent as real messages rather than
as text — the model picks up the conversation instead of starting cold. A
prompt that already places [[ transcript ]] or [[ full_transcript ]] itself
is left alone, so you keep control over how much history is sent and where it
goes. Use LLM.compact() to shorten a long history first.
Tools are passed on the prompt as a list of maps, for example
%{type: "task", task: "get_weather"}. This is the same shape as the tools:
key in a prompts YAML file.
The declared task follows the same rules as LLM tool calling:
a clear description, an input schema (in:), an output schema (out:), and a
task implementation. When the model calls the tool, the task runs and its
return value is fed back into the agent.
How the loop behaves¶
After agentloop, the current dialog waits while the agent runs. User messages
are forwarded into the loop until a dialog tool stops it (see Stopping the
loop).
- Assistant text is sent to the user as normal bot messages on chat and phone channels.
- Text user messages are forwarded into the running agent. Attachments, locations, and other non-text input are not supported yet.
- The agent is not supported on process frontends (
master/group).
Dialog tools and scripted handoff¶
When the model calls a dialog tool (not a task), the runtime pauses the agent loop and runs that Bubblescript dialog on the main interpreter. While the dialog is active:
- User messages are handled by the dialog (
ask, etc.) and are not forwarded to the agent. - Bot
saytext during the dialog is recorded for context but not sent to the agent as separate turns.
When the dialog returns (falls off the end or returns), the agent resumes. The
transcript of user and bot lines from that handoff is returned to the model as
the dialog tool result. Nested invokes inside the dialog keep the agent paused
until the whole tool dialog has popped back to the agentloop await dialog.
The agent waits for the dialog to complete; there is no fixed dialog-tool timeout. Stopping the session or agent loop cancels the wait through the normal process lifecycle.
If the session is stopped while a dialog handoff is in progress, both the interpreter stack and agent state (including the in-progress handoff) are persisted and restored on the next session start.
continue inside a dialog tool still
ends the entire agent loop — it is not a resume from handoff.
Stopping the loop¶
A dialog tool that runs
continue exits the agent
loop. The agent process is torn down, and execution resumes at the
next statement after agentloop. Any dialog tool that continues
has this same effect. Task tools cannot stop the loop this way:
continue only applies to dialogs.
Errors and unsupported nesting¶
Agent-loop failures use normal Bubblescript error handling. Define an
__error__ dialog when the bot needs to present a custom message or recovery
path:
dialog main do
agentloop(@prompts.support_agent)
dialog __error__ do
log("Agent loop failed: #{error.message}")
say("Sorry, I cannot continue this conversation right now.")
end
end
The following conditions raise a runtime error:
- the value passed to
agentloopis not an LLM prompt; - rendering or preparing the prompt fails;
- the frontend or selected engine is unsupported;
- the running agent terminates because of a provider or process failure; and
- another
agentloopis started while one is already active.
Dialog tools may invoke nested Bubblescript dialogs, but they cannot start a
nested agent loop. Attempting that raises nested agentloop is not supported.
Legacy engine¶
agentloop(:legacy, prompt) selects the legacy engine, available for
migration only. It requires include: [legacy_agentloop] in app.dev and
replaces the provider and model on the supplied prompt with @llm_smart_model.
It also exposes the overridable pre_perform_tool_calls dialog and the
on_agent_llm_error task hook, which the default engine does not.