Extension API Reference¶
This page is the reference companion to Writing extensions and Events. It documents the
objects an extension’s register(mb) function receives and the types it works with.
register(mb) is called once at startup with an ExtensionContext
built from the module’s [extensions.config.<module>] section. Import and registration failures
are fatal — a module that cannot load stops startup rather than leaving the agent silently unable
to use its tools.
ExtensionContext¶
- class minibot.app.extensions.ExtensionContext(name, config, settings, event_bus, logger, entrypoint='daemon', vault=None, agent_registry=None, agent_management=None, refresh_tools=None, tools=<factory>, tool_derivers=<factory>, tool_wrappers=<factory>, subscriptions=<factory>, services=<factory>, routes=<factory>, prompt_fragments=<factory>, pages=<factory>)[source]¶
Handed to an extension’s
register(mb)function.Deliberately narrow: the container is a class-level singleton, and passing it whole would make every one of its fields public API.
entrypointis"daemon","console", or"worker". Channel extensions must contribute nothing outside the daemon: those entrypoints do not drive channel services.vaultis the unlocked credential vault, orNonewhen[vault] enabledis false. Resolve a secret only into a destination the admin configured; never expose one to the LLM.- Parameters:
name (str)
settings (Settings)
event_bus (EventBus)
logger (logging.Logger)
entrypoint (ExtensionEntrypoint)
vault (SecretVault | None)
agent_registry (AgentRegistry | None)
agent_management (AgentManagementService | None)
refresh_tools (Callable[[], Awaitable[None]] | None)
tools (list[ToolBinding])
tool_derivers (list[ToolDeriver])
tool_wrappers (list[ToolWrapper])
services (list[ExtensionService])
routes (list[RouteSpec])
prompt_fragments (list[_PromptFragment])
- settings: Settings¶
- event_bus: EventBus¶
- logger: logging.Logger¶
- entrypoint: ExtensionEntrypoint = 'daemon'¶
- tools: list[ToolBinding]¶
- services: list[ExtensionService]¶
- tool(func)[source]¶
Register
funcas a tool: name from__name__, description from its docstring, parameters from its first argument’s pydantic model.For anything this does not cover — a name that is not a Python identifier, a hand-written schema, a description loaded from a file — build the
ToolBindingyourself and pass it toadd_tool.- Parameters:
func (Callable[[Any, ToolContext], Awaitable[Any]])
- Return type:
Callable[[Any, ToolContext], Awaitable[Any]]
- add_tool(bindings)[source]¶
- Parameters:
bindings (ToolBinding | Sequence[ToolBinding])
- Return type:
None
- add_tool_deriver(deriver)[source]¶
Contribute tools built from the tool list an agent ends up with.
add_toolregisters before any agent exists, so a tool that must act on other tools (call them, describe them) cannot be built there.deriverruns once per agent, with the final list: after approval, event and output-spill wrapping,tools_allow/tools_denyand the removal of the delegation tools. It returns the extra bindings, which are then approval- and event-wrapped like any tool and filtered by the same agent policy. The input list does not contain them, and it is rebuilt whenever the tool list is, so a deriver must be a pure function of its argument. Runs in the daemon, the console and task workers.A deriver may return a
DerivedToolinstead of a plain binding to take over input tools: while the derived tool survives the agent’s policy, the tools itreplacesare no longer shown to the model.- Parameters:
deriver (ToolDeriver)
- Return type:
None
- add_tool_wrapper(wrapper)[source]¶
Wrap the handlers of every tool an agent can call, to check or change calls and results.
wrappergets the bindings and anApproverthat asks the user in the chat the call came from (denied where the channel has no approval buttons), and returns one binding per input binding, in the same order and with the same tool names; keepspill_after_chars(dataclasses.replace). It covers core, extension, MCP and derived tools alike, in the daemon, the console and task workers, and runs again whenever the tool list is rebuilt, so it must be a pure function of its arguments. Wrappers sit innermost: they see the raw result before output spill, while[tools.approval]and tool-call events stay outside them. Calls nested in a codemode script go through them too, withcontext.parent_call_idset.- Parameters:
wrapper (ToolWrapper)
- Return type:
None
- add_service(service)[source]¶
- Parameters:
service (ExtensionService)
- Return type:
None
- add_route(path, handler, methods=('GET',))[source]¶
Serve
handleratpathon the built-in HTTP server ([http] enabled).The handler takes a
starlette.requests.Requestand returns aResponse. Routes are collected once every extension has registered, so ordering between extensions is irrelevant.
- add_prompt_fragment(text, *, tool_names=())[source]¶
Append
textto the main agent’s system prompt.For guidance the model needs before it picks a tool and that no single tool description can carry – an MCP server’s
instructions, a backend’s conventions. Tool-specific wording belongs in that tool’s description, not here: this is paid for on every single turn. Whentool_namesis set, the fragment is included only if one of those tools is attached.
- add_page(path, label, handler, methods=('GET',), *, icon='file')[source]¶
Like
add_route, plus an entry in the web UI’s navigation menu underlabel.iconis a Lucide icon name (https://lucide.dev/icons), shown next to the label. An extension that bails out ofregister()never calls this, so a disabled extension drops out of the menu on its own — the menu needs no separate notion of what is enabled.
ExtensionService¶
Objects passed to mb.add_service implement this protocol. They are started when the entrypoint
starts and stopped on shutdown.
Tools¶
- class minibot.llm.tools.base.ToolBinding(tool, handler, spill_after_chars=None)[source]¶
A tool schema and its handler.
spill_after_charslets a tool keep larger results inline than[tools.tool_output_spill]spill_after_chars(it never lowers it). Where spill cannot run, the tool’s results are cut to it.- Parameters:
- tool: Tool¶
- class minibot.llm.tools.base.ToolContext(owner_id: 'str | None' = None, channel: 'str | None' = None, channel_capabilities: 'ChannelCapabilities' = <factory>, chat_id: 'int | None' = None, user_id: 'int | None' = None, turn_id: 'str | None' = None, task_handoff_callback: 'Callable[[str], Awaitable[None]] | None' = None, task_chain_depth: 'int' = 0, claim_task_continuation: 'Callable[[], bool] | None' = None, release_task_continuation: 'Callable[[], None] | None' = None, call_id: 'str | None' = None, parent_call_id: 'str | None' = None)[source]¶
- Parameters:
owner_id (str | None)
channel (str | None)
channel_capabilities (ChannelCapabilities)
chat_id (int | None)
user_id (int | None)
turn_id (str | None)
task_handoff_callback (Callable[[str], Awaitable[None]] | None)
task_chain_depth (int)
release_task_continuation (Callable[[], None] | None)
call_id (str | None)
parent_call_id (str | None)
- channel_capabilities: ChannelCapabilities¶
Events¶
Subscribe to any of these with @mb.on(EventType) or mb.on(EventType, handler). See
Events for when each one fires and what its payload carries. Handlers are async and lossy:
a slow handler drops events rather than blocking the pipeline.
- class minibot.core.events.MessageEvent(*, event_id=<factory>, event_type='message', message)[source]¶
-
- message: ChannelMessage¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.OutboundEvent(*, event_id=<factory>, event_type='outbound', response)[source]¶
-
- response: ChannelResponse¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.OutboundFileEvent(*, event_id=<factory>, event_type='outbound_file', response)[source]¶
-
- response: ChannelFileResponse¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.OutboundFormatRepairEvent(*, event_id=<factory>, event_type='outbound_format_repair', response, parse_error, attempt=1, chat_id, channel, user_id=None, capabilities=<factory>)[source]¶
- Parameters:
- response: ChannelResponse¶
- capabilities: ChannelCapabilities¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.SystemEvent(*, event_id=<factory>, event_type='system', payload=None)[source]¶
-
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.TurnStartedEvent(*, event_id=<factory>, event_type='turn_started', turn_id, channel, chat_id=None, user_id=None, available_tools=<factory>)[source]¶
Emitted when the dispatcher begins processing an inbound message.
- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.TurnInputPreparedEvent(*, event_id=<factory>, event_type='turn_input_prepared', turn_id, channel, chat_id=None, text='', available_tools=<factory>)[source]¶
Emitted once the turn’s opening message is ready for the model.
textis the message text followed by the automatic transcriptions of its own audio. Messages folded into the running turn and audio transcribed later by a tool are not included.available_toolsnames the tools the main agent can use for this turn.- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.TurnCompletedEvent(*, event_id=<factory>, event_type='turn_completed', turn_id, channel, chat_id=None, should_reply=True, llm_provider=None, llm_model=None, token_trace=<factory>, compaction_performed=None, tools_used=<factory>, task_handoff=False)[source]¶
Emitted once a turn is fully done.
Fires after the response has been dispatched to the channel, or deliberately suppressed when
should_replyis false — never before.- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.TurnFailedEvent(*, event_id=<factory>, event_type='turn_failed', turn_id, channel, chat_id=None, error)[source]¶
Emitted when a turn raised before producing a response.
- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.TurnStopRequestedEvent(*, event_id=<factory>, event_type='turn_stop_requested', channel, chat_id, user_id=None)[source]¶
Emitted when the owner asks to stop the turn running in a chat.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.ReasoningEvent(*, event_id=<factory>, event_type='reasoning', text, step, turn_id=None, owner_id=None, channel=None, chat_id=None)[source]¶
Emitted as soon as one provider step returns reasoning, before the turn finishes.
Reasoning is also attached to the final response metadata; this event exists so a channel can show thinking while the turn is still running instead of only after it completes.
- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.ToolCallEvent(*, event_id=<factory>, event_type='tool_call', phase, call_id=<factory>, tool_name, turn_id=None, owner_id=None, channel=None, chat_id=None, detail='', error=None, parent_call_id=None)[source]¶
Emitted around every tool handler invocation.
detailis already redacted and clipped byminibot/llm/tools/tool_call_display.pybefore this event is published — the raw argument payload (which can hold credentials, e.g.http_requestheaders,bashenv,python_executecode) never leaves the tool-execution layer. Safe to log, persist, or forward to any subscriber, including extensions.parent_call_idis thecall_idof the tool call that made this one, for tools that call other tools (run_tools_script);Nonefor calls the model made directly.- Parameters:
- phase: Literal['started', 'completed', 'failed']¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class minibot.core.events.ToolApprovalRequestedEvent(*, event_id=<factory>, event_type='tool_approval_requested', approval_id, tool_name, channel, chat_id, detail='', requester_user_id=None)[source]¶
A dangerous tool call waits for the user.
detailis redacted likeToolCallEvent’s.- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].