Configuration Reference¶
MiniBot is configured via a config.toml file, most easily created with the interactive
wizard (see Getting Started):
minibot configure
To hand-write or inspect the file directly instead, start from the provided example:
cp config.example.toml config.toml
Values follow standard TOML syntax. Byte-size fields accept human-readable
strings (e.g. "64KB", "5MB") in addition to raw integers.
Environment variables¶
Use ${NAME} in any string value to read a variable from MiniBot’s process environment.
References work in nested tables and lists, including provider headers and extension configuration:
[providers.openai]
api_key = "${OPENAI_API_KEY}"
base_url = "${API_BASE_URL}/v1"
[providers.openai.headers]
Authorization = "Bearer ${PROXY_TOKEN}"
[runtime]
agent_timeout_seconds = "${AGENT_TIMEOUT_SECONDS}"
[channels.telegram]
enabled = "${TELEGRAM_ENABLED}"
allowed_chat_ids = ["${OWNER_CHAT_ID}"]
Names follow [A-Za-z_][A-Za-z0-9_]*. Multiple references in one value are supported.
Write $${NAME} to produce literal ${NAME}. Bare $NAME and unsupported expressions such as
${NAME:-default} remain unchanged. Table names and keys are never expanded.
TOML is parsed before substitution, so quotes, backslashes, and newlines in environment values are inserted verbatim without changing the TOML structure. Both TOML basic and literal strings are expanded. Inserted values are not expanded again. Non-string TOML values stay unchanged; quoted references for numeric, boolean, and byte-size fields use the existing schema’s conversions and validation. Environment values are not parsed as TOML lists or tables.
An unset variable raises an error naming the variable and configuration path, including list indexes,
without showing its value. All sections are expanded, including disabled providers and tools.
A variable set to an empty string produces "" and follows the setting’s existing validation.
Supply variables to each process that loads configuration: the daemon, console, task workers, and
minibot configure. MiniBot does not load .env files or execute shell expressions. Changes to
the environment take effect the next time settings are loaded. MINIBOT_CONFIG still selects the
configuration file when no explicit path is supplied.
minibot configure retains the original references for unchanged values, including list entries,
and uses resolved credentials for model discovery. References entered in credential prompts are
saved literally. Loading settings never rewrites the file; literal secrets remain plain text on disk.
For Python callers, expansion occurs in Settings.from_dict() and Settings.from_file() before
normalization and validation, without mutating the input dictionary. Direct Settings(...) and
Settings.model_validate(...) calls do not expand references.
Vault references¶
${secret:NAME} resolves to an entry in the encrypted credential vault, so a credential never has
to sit in plain text on disk. This matters because config.toml is readable by the bash tool,
which has no filesystem jail — the vault file is not.
[providers.openai]
api_key = "${secret:OPENAI_API_KEY}"
[channels.telegram]
bot_token = "${secret:telegram}"
[[tools.mcp.servers]]
name = "linear"
headers = { Authorization = "Bearer ${secret:linear}" }
Names follow [A-Za-z_][A-Za-z0-9_.-]*, matching what the vault document accepts as a key.
$${secret:NAME} produces a literal ${secret:NAME}. The resolved value is injected into the
in-memory Settings instance only; nothing is written back to the file.
Resolution happens in a second pass, after ${ENV_VAR} expansion and after the vault is
unlocked at startup — the vault’s location comes from configuration, so it cannot be known before
the first pass. Consequences worth knowing:
[vault]settings themselves cannot use${secret:}; doing so is an error.[vault] enabledmust betruewherever a reference appears, or startup fails naming the reference.A configuration with no references never reads the vault for expansion at all.
Task workers reload configuration in their own process and receive the vault contents over the in-memory pipe from the daemon, so references resolve there too.
minibot configurepreserves references rather than resolving them, exactly as it does for${ENV_VAR}.
${secret:NAME} and the typed [[tools.mcp.servers]] auth_secret field both read the vault and
both remain supported; auth_secret is the shorthand for the common Authorization: Bearer
case. Setting both on the same server is an error.
This protects the credential at rest. Once resolved, the value lives in the daemon’s memory just
as an ${ENV_VAR} one does; see Security & Sandboxing for the full threat model.
Runtime¶
- class minibot.adapters.config.schema.RuntimeConfig(*, log_level='INFO', environment='development', agent_timeout_seconds=120, owner_id='primary')[source]¶
Top-level runtime settings. TOML section:
[runtime]log_level— root log level (default:"INFO").environment— label used in log context (default:"development"). It also drives the built-in HTTP UI:"development"and"debug"make the server sendCache-Control: no-storefor dashboard static assets, so a normal reload picks up edits; any other value (for example"production") leaves normal caching on.agent_timeout_seconds— hard wall-clock timeout for any agent turn (min/default:120).owner_id— the person this MiniBot assists (default:"primary").
MiniBot is a personal assistant for exactly one owner.
owner_idis a constant of the deployment: it is never derived from a message, a task payload or any caller-supplied field, and it becomesToolContext.owner_idfor every tool call on every entrypoint. It therefore owns long-term memory, the relation graph, scheduled jobs and the RAG corpus.Conversations are scoped separately, per channel and chat. One owner, many chat sessions: a private Telegram chat, a group and the console keep independent history while sharing that one owner’s long-term data.
Channels¶
- class minibot.adapters.config.schema.TelegramChannelConfig(*, enabled=True, bot_token='', allowed_chat_ids=<factory>, allowed_user_ids=<factory>, mode='long_polling', webhook_url=None, require_authorized=True, media_enabled=True, max_photo_bytes=5242880, max_document_bytes=10485760, max_total_media_bytes=12582912, max_attachments_per_message=3, allowed_document_mime_types=<factory>, format_repair_enabled=True, format_repair_max_attempts=1)[source]¶
Telegram channel settings. TOML section:
[channels.telegram]bot_token— BotFather token (required).allowed_chat_ids/allowed_user_ids— access control lists.mode—"long_polling"(default) or"webhook".webhook_url— required whenmode = "webhook".require_authorized— reject messages from unlisted IDs (default:true).media_enabled— accept photo/document attachments (default:true).max_photo_bytes/max_document_bytes/max_total_media_bytes— per-message size caps.max_attachments_per_message— attachment count cap (default:3).allowed_document_mime_types— MIME whitelist; empty means all types are allowed.format_repair_enabled— auto-repair malformed Markdown before sending (default:true).
- Parameters:
enabled (bool)
bot_token (str)
mode (str)
webhook_url (str | None)
require_authorized (bool)
media_enabled (bool)
max_photo_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
max_document_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
max_total_media_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
format_repair_enabled (bool)
LLM¶
- class minibot.adapters.config.schema.LLMMConfig(*, provider='openai', api_key='', base_url=None, model='gpt-4o-mini', http2=False, temperature=None, max_new_tokens=None, max_tool_iterations=15, request_timeout_seconds=45, sock_connect_timeout_seconds=10, sock_read_timeout_seconds=45, retry_attempts=3, retry_delay_seconds=2.0, system_prompt='You are Minibot, a helpful assistant.', system_prompt_file='./prompts/main_agent_system.md', prompts_dir='./prompts', reasoning_effort=None, reasoning_summary=None, main_responses_state_mode='full_messages', agent_responses_state_mode='previous_response_id', responses_state_mode='full_messages', prompt_cache_enabled=True, prompt_cache_retention=None, strip_logs=False, extra_headers=<factory>, auth_path=None, openrouter=OpenRouterLLMConfig(attribution_enabled=True, models=[], provider=None, reasoning_enabled=None, plugins=[]), xai=XAILLMConfig(web_search_enabled=False, x_search_enabled=False, web_search=XAIWebSearchConfig(allowed_domains=[], excluded_domains=[], enable_image_understanding=False), x_search=XAIXSearchConfig(allowed_x_handles=[], excluded_x_handles=[], from_date=None, to_date=None, enable_image_understanding=False, enable_video_understanding=False)))[source]¶
Main LLM settings. TOML section:
[llm]provider— provider name:"openai","anthropic","openrouter","xai", etc.api_key— provider API key.base_url— optional base URL (for proxies or OpenAI-compatible local servers).model— model identifier (default:"gpt-4o-mini").temperature— sampling temperature (nulluses provider default).max_new_tokens— max tokens to generate per turn.max_tool_iterations— maximum tool-call rounds before forcing a final answer (default:15).request_timeout_seconds— HTTP timeout per LLM request (min:45).retry_attempts— extra attempts for a failed HTTP request, and for a main-agent step whose response ends with statusfailedor has no text and no tool calls (default:3). Context-length and invalid-prompt failures are not retried.retry_delay_seconds— initial delay between attempts; step retries back off exponentially up tomin(4 * retry_delay_seconds, 10)seconds (default:2.0).system_prompt— inline system prompt (overridden bysystem_prompt_file).system_prompt_file— path to the main system prompt markdown file.prompts_dir— directory for runtime prompt fragments.reasoning_effort— reasoning budget hint for supported models (e.g."high").reasoning_summary— request a plaintext reasoning summary from Responses models ("auto"/"concise"/"detailed").main_responses_state_mode— how conversation state is passed for the main agent ("full_messages"or"previous_response_id").prompt_cache_enabled— enable provider-side prompt caching (default:true).strip_logs— shorten selected fields in the provider raw-response debug log (default:false).extra_headers— extra HTTP headers sent on every provider request (usually inherited from[providers.<name>.headers]).auth_path— path to an OAuth credentials file, used by thechatgpt_codexprovider (usually inherited from[providers.chatgpt_codex.auth_path]; defaults to~/.minibot/auth_codex.json).openrouter— OpenRouter-specific routing overrides ([llm.openrouter]).xai— xAI web/X search integration ([llm.xai]).
- Parameters:
provider (str)
api_key (str)
base_url (str | None)
model (str)
http2 (bool)
temperature (float | None)
request_timeout_seconds (int)
retry_delay_seconds (float)
system_prompt (str)
system_prompt_file (str | None)
prompts_dir (str)
reasoning_effort (str | None)
reasoning_summary (str | None)
main_responses_state_mode (Literal['full_messages', 'previous_response_id'])
agent_responses_state_mode (Literal['full_messages', 'previous_response_id'])
responses_state_mode (Literal['full_messages', 'previous_response_id'])
prompt_cache_enabled (bool)
prompt_cache_retention (Literal['in-memory', '24h'] | None)
strip_logs (bool)
auth_path (str | None)
openrouter (OpenRouterLLMConfig)
xai (XAILLMConfig)
Providers¶
See Providers for setup examples and compatible endpoint guidance.
- class minibot.adapters.config.schema.ProviderConfig(*, api_key='', base_url=None, headers=<factory>, auth_path=None, api_format=None, models=<factory>)[source]¶
Named LLM provider credentials. TOML section:
[providers.<name>]Used to supply API keys and base URLs for secondary providers referenced by agent definitions (
model_provider).api_key— provider API key.base_url— optional base URL override (e.g. for proxies or local endpoints).headers— extra HTTP headers sent on every request to this provider. OpenCode Go requiresx-opencode-sessionand rejects requests without it (MissingSessionID).auth_path— used only by[providers.chatgpt_codex]: path to the ChatGPT Codex OAuth credentials file written byminibot codex login. Defaults to~/.minibot/auth_codex.jsonwhen unset.api_format— which API dialect this endpoint speaks, and therefore which client is built:"openai","openai_responses","openrouter","claude","google"or"chatgpt_codex". Required unless the section name is itself one of those values, which lets several endpoints of the same format coexist:[providers.opencode_go]withapi_format = "openai_responses"alongside[providers.zai]withapi_format = "openai".models— advisory list of model ids this endpoint serves. Not validated against the endpoint; it is what agents and runtime delegation overrides are offered to choose from.
OpenAI-compatible third-party endpoints (set under
[providers.openai]with[llm].provider = "openai", or[providers.openai_responses]withprovider = "openai_responses"):OpenCode Zen (pay-as-you-go):
base_url = "https://opencode.ai/zen/v1", e.g.model = "kimi-k2.7-code".OpenCode Go (subscription):
base_url = "https://opencode.ai/zen/go/v1", e.g.model = "mimo-v2.5". Some models (gpt-5.6-luna,grok-4.6) are only exposed via the Responses variant of that base URL.z.ai GLM Coding Plan:
base_url = "https://api.z.ai/api/coding/paas/v4"(Chat Completions, recommended — documented function calling/streaming support) withmodel = "glm-5.3"or the cheaper"glm-5.3-flash". A Responses-style endpoint also exists athttps://api.z.ai/api/v1but z.ai’s docs don’t confirm parity with OpenAI Responses semantics (previous_response_id,reasoning), so prefer Chat Completions unless you’ve verified it works for your use case.
Memory¶
[memory].notify_compaction_updates accepts "off" (no notices), "brief"
(running and done notices), or "full" (notices plus the summary). Existing
configurations must replace false with "off" and true with "full";
the old boolean values now fail validation at startup.
- class minibot.adapters.config.schema.MemoryConfig(*, backend='sqlite', sqlite_url='sqlite+aiosqlite:///./data/minibot.db', max_history_messages=None, max_history_tokens=None, context_ratio_before_compact=0.95, notify_compaction_updates='off')[source]¶
Conversation history memory settings. TOML section:
[memory]backend— storage backend (currently only"sqlite").sqlite_url— SQLite database URL (default:"sqlite+aiosqlite:///./data/minibot.db").max_history_messages— hard cap on stored messages per conversation (null= unlimited).max_history_tokens— token budget for history sent to the LLM (null= unlimited).context_ratio_before_compact— fraction of context window used before triggering automatic compaction (default:0.95).notify_compaction_updates— what the chat receives when history is compacted:"off"nothing (default),"brief"only the short “running” and “done” notices,"full"those notices plus the compaction summary.
Orchestration¶
[orchestration.specialists].enabled (default true) is the switch for using owner-defined
specialist agents. With it off, the roster, fetch_agent_info and named delegation all
disappear, while the generic task worker stays available as long as [tasks] is enabled.
Specialists run on the task backend, so the default follows [tasks].enabled: with tasks off and
this key omitted, the roster is disabled. Setting it to true explicitly while
[tasks].enabled = false fails config loading instead of exposing a roster nothing can use.
[orchestration.agent_management] is opt-in and off by default: reload exposes
reload_agents for hand-edited files, and write exposes controlled create/update/delete
tools for model-authored definitions stored in its own directory, which must not overlap
[orchestration].directory.
Model-authored agents are bounded by an owner-configured ceiling, empty by default so a managed
agent is granted nothing until the owner lists it. The ceiling itself may use fnmatch patterns
(file*), but a managed definition may not: every entry in its tools_allow has to be an exact
tool name, because only an exact name can be checked against a pattern without enumerating the tool
list. A managed definition may not use tools_deny at all — it grants every tool except the
listed ones, so no ceiling can bound it — and it claims MCP servers through mcp_servers rather
than through tool names. providers restricts which model provider a managed agent may target,
including a spawn_task or delegate model override; the provider the daemon already runs on is always
allowed, since reusing it is not an escalation. Owner-authored agents are not subject to the
ceiling, so their permissions stay exactly as the owner wrote them.
- class minibot.adapters.config.schema.OrchestrationConfig(*, directory='./agents', tool_ownership_mode='shared', shared_mcp_servers=<factory>, main_tool_use_guardrail='disabled', main_agent=MainAgentConfig(name='minibot', tools_allow=[], tools_deny=[]), specialists=<factory>, agent_management=<factory>)[source]¶
Multi-agent orchestration settings. TOML section:
[orchestration]directory— path to agent definition files (default:"./agents").tool_ownership_mode— how tools are shared between agents:"shared"(default),"exclusive", or"exclusive_mcp".shared_mcp_servers— MCP server names whose tools stay visible to the main agent even when a specialist claims them under an exclusive ownership mode (default: empty).main_tool_use_guardrail— optional guardrail before tool execution:"disabled"(default) or"llm_classifier".main_agent— tool allow/deny policy for the main agent ([orchestration.main_agent]).specialists— whether owner-defined specialists are used at all ([orchestration.specialists]).agent_management— optional runtime reload and model-authored definitions ([orchestration.agent_management]).
- Parameters:
directory (str)
tool_ownership_mode (Literal['shared', 'exclusive', 'exclusive_mcp'])
main_tool_use_guardrail (Literal['disabled', 'llm_classifier'])
main_agent (MainAgentConfig)
specialists (SpecialistsConfig)
agent_management (AgentManagementConfig)
- class minibot.adapters.config.schema.SpecialistsConfig(*, enabled=True)[source]¶
Specialist agent settings. TOML section:
[orchestration.specialists]enabled— use owner-defined specialist agents (default:true). Off drops the roster,fetch_agent_infoand named delegation, while the generic task worker stays available whenever[tasks]is enabled. Specialists run on the task backend, so the default follows[tasks].enabled: with tasks off and this key omitted, the roster is disabled. Setting it totrueexplicitly while[tasks].enabled = falsefails config loading rather than exposing a roster nothing can act on.
- Parameters:
enabled (bool)
- class minibot.adapters.config.schema.AgentManagementConfig(*, reload=False, write=False, directory='./data/agents', tools_allow=<factory>, mcp_servers=<factory>, providers=<factory>)[source]¶
Runtime agent management. TOML section:
[orchestration.agent_management]reload— exposereload_agentsso an owner can hand-edit definition files and re-read them (default:false).write— expose controlled create/update/delete tools for model-authored definitions (default:false). A successful write refreshes the registry itself, so this does not requirereload.directory— where model-authored definitions live (default:"./data/agents"). Must not overlap[orchestration].directory, so owner- and model-authored files stay distinguishable.tools_allow— fnmatch patterns naming the native tools a managed agent may grant itself (default: empty, which grants none). A managed definition’s owntools_allowmust list exact names, and it may not usetools_deny.mcp_servers— MCP server names a managed agent may claim (default: empty). MCP access is claimed here, never through a tool name.providers— providers a managed agent may target, including aspawn_taskoverride (default: empty, meaning the main[llm]provider only, which is always allowed).
Decision¶
[decision] is opt-in and off by default. When enabled, every main-agent turn also asks a
decision model (a <base_url>/decisions endpoint, OpenRouter’s /api/alpha/decisions by default,
not /chat/completions) four fixed
questions about the user’s message: route (answer directly, use tools, or delegate a task),
needs_memory, needs_web and complexity. The request runs in parallel with the real
generation and is logged as a turn decision line next to the tools the agent actually used and
whether the turn was handed off to a task, so the model’s choices can be compared with reality before
anything acts on them. It never changes a reply, adds no latency, and a timeout or any failure is
logged and ignored.
It runs as the bundled minibot.extensions.integrations.decision extension, listening to the turn
events (see Events), so it is active in the daemon and console but not in the task worker. It is
also asked for turns that end before the model is called, such as unsupported media. It is a lossy
observer: an event dropped by the event bus means no turn decision line for that turn, and at most 32
turns wait for an answer at once.
The question is asked once the opening message is ready for the model, so a voice note counts by what
it says: its automatic transcription is appended to the message text. Only the audio of that message
counts; messages sent while the turn runs, audio transcribed later by the transcribe_audio tool, and
audio longer than the automatic-transcription limit are left out, and nothing is transcribed just for
the decision.
Every turn sends the user’s message text, the automatic transcriptions of its audio, and the names of the main agent’s tools to OpenRouter and the model provider behind it, so enable it only where that is acceptable and check the provider’s data policy for the chosen model.
Keep the key in the vault:
[decision]
enabled = true
model = "~typesafe/jev-latest"
base_url = "https://openrouter.ai/api/alpha"
api_key = "${secret:openrouter_api_key}"
The default ~typesafe/jev-latest bills a small per-request fee. inception/mercury-decide:free is a
drop-in alternative at no cost; free model variants may follow a different data policy.
- class minibot.adapters.config.schema.DecisionConfig(*, enabled=False, model='~typesafe/jev-latest', base_url='https://openrouter.ai/api/alpha', api_key='', timeout_seconds=3.0)[source]¶
Optional decision model consulted once per main-agent turn. TOML section:
[decision]enabled— consult the decision model on every turn (default:false). Today this only records what the model would have chosen next to what the agent actually did; it never changes a reply.model— an OpenRouter decisions model (default:"~typesafe/jev-latest", billed per request;"inception/mercury-decide:free"also works). Each turn sends the user’s message text and the main agent’s tool names to OpenRouter.base_url— root of a decisions API; requests go to<base_url>/decisions(default:"https://openrouter.ai/api/alpha").api_key— OpenRouter API key, required when enabled. Accepts${secret:NAME}.timeout_seconds— per-request timeout; a timeout or any failure is logged and the turn goes on untouched (default:3.0).
Scheduler¶
- class minibot.adapters.config.schema.ScheduledPromptsConfig(*, enabled=True, sqlite_url='sqlite+aiosqlite:///./data/scheduled_prompts.db', poll_interval_seconds=60, lease_timeout_seconds=120, batch_size=10, max_attempts=3, min_recurrence_interval_seconds=60, pool_size=5, echo=False)[source]¶
Scheduler persistence and polling settings. TOML section:
[scheduler.prompts]enabled— enable the scheduler (default:true).sqlite_url— SQLite database URL for job storage.poll_interval_seconds— how often due jobs are checked (default:60).lease_timeout_seconds— lease duration before a stalled job is retried (default:120).batch_size— max jobs processed per poll cycle (default:10).max_attempts— retries before a job is marked failed (default:3).min_recurrence_interval_seconds— floor for recurring job intervals (default:60).
Logging¶
- class minibot.adapters.config.schema.LoggingConfig(*, structured=True, logfmt_enabled=True, log_level='INFO', kv_separator='=', record_separator=' ')[source]¶
Structured logging settings. TOML section:
[logging]structured— enable structured (JSON-like) log output (default:true).logfmt_enabled— use logfmt key=value format instead of JSON (default:true).log_level— log level for the logging subsystem (default:"INFO").kv_separator— separator between key and value in logfmt (default:"=").record_separator— separator between fields in logfmt (default:" ").
Tasks¶
[tasks].backend selects the queue: "sqlite" (no broker, polled, survives a crash via lease
expiry) or "rabbitmq" (needs a broker and the rabbitmq extra, pushed). [tasks.sqlite]
configures the first, [rabbitmq] the second.
The default is "sqlite". A config written before that default changed and left backend
unset now uses the SQLite queue; set backend = "rabbitmq" explicitly to keep using the broker.
By default a worker starts with no context: it sees only its system prompt, the task prompt and
context_json, and the spawn_task and delegate descriptions tell the main agent so. [tasks].history = true
gives worker conversations a persistent history instead. It needs backend = "sqlite".
A conversation is a named agent in a chat, or that agent plus a session name the main agent passes to
spawn_task or delegate (for example one per client). A general worker only gets a conversation when it is given a
session; otherwise it starts empty as before. Every finished task appends its prompt and answer to the
conversation, each capped at 8000 characters (failed or timed-out tasks append nothing), and the next task
receives it before its own prompt. Tasks of one conversation run one at a time: the queue does not lease a
task while another task of the same conversation is leased or running, so a later task waits and then sees
the earlier result. Different sessions run in parallel. When the stored history grows past roughly 16 KB
and the model’s context limit is known, the worker summarizes it before running and the summary replaces
it; when the limit is unknown nothing is summarized and the worker only receives the newest messages that
fit in about 32 KB. Tasks without a chat (no chat_id) never get a history. fresh = true on spawn_task or delegate clears the conversation before a task, and the tool description
switches to explain all this:
[tasks]
history = true
The history lives in the [memory] database under the session
task:<channel>:<chat_id>:<agent_name>[:<session>] (task_worker for the general worker), so it is
visible in the web console’s session list.
- class minibot.adapters.config.schema.TasksConfig(*, enabled=True, backend='sqlite', worker_timeout_seconds=1800, worker_max_steps='unlimited', worker_max_tool_calls='unlimited', max_concurrent_workers=4, delegation_mode='auto', history=False, worker_extensions=<factory>, sqlite=SqliteTaskQueueConfig(sqlite_url='sqlite+aiosqlite:///./data/tasks.db', poll_interval_seconds=5, lease_timeout_seconds=2100, batch_size=4, max_attempts=3, done_retention_seconds=2592000, pool_size=5, echo=False))[source]¶
Async task system settings. TOML section:
[tasks]Gates both the task consumer service and the
spawn_task/delegate/cancel_task/list_tasks/get_tasktools.spawn_taskanddelegateare also the only ways to hand work to a specialist agent, so disabling this section turns multi-agent orchestration off entirely.enabled— enable the async task system (default:true; thesqlitebackend needs no broker and no extra).backend— queue backend:"sqlite"(default; no broker required) or"rabbitmq"(see[rabbitmq]).worker_timeout_seconds— hard per-task processing timeout (default:1800).worker_max_steps— optional execution-step ceiling;"unlimited"disables it (default).worker_max_tool_calls— optional tool-call ceiling;"unlimited"disables it (default).max_concurrent_workers— maximum parallel task handlers (default:4).delegation_mode— which delegation tools the main agent gets."auto"(default) attaches both:delegatewhen the model needs the result to keep working (it comes back as a new turn) andspawn_taskwhen the worker’s answer is the final deliverable (it goes straight to the chat)."delegate"attaches onlydelegate, so every result returns to the main agent as a new turn; past the continuation limit it delivers straight to the chat instead of failing. Replaces the removedcontinue_turn_mode = "always";continue_turn_modeandcontinue_turn_defaultare rejected.history— keep a persistent history per worker conversation (default:false; needsbackend = "sqlite"). A conversation is a named agent in a chat,task:<channel>:<chat_id>:<agent_name>, or that plus asessionname passed tospawn_task; general workers only get one when given asession. Each task sees the conversation’s earlier prompts and answers (each capped at 8000 characters), stored in the[memory]database, and tasks of one conversation run one at a time, later ones waiting in the queue. A large history is summarized before the next task when the model’s context limit is known, andspawn_taskgainsfreshto clear it first. Withfalseevery worker starts with no history.worker_extensions— bundled tool extensions a worker loads on top of the scheduler and utility ones (default:[])."kv_memory"adds the[tools.kv_memory]tools,"rag"the[tools.rag]tools and"codemode"therun_tools_scripttool ([tools.codemode]), which calls the tools that specialist itself has. Only a specialist that names them intools_allowgets them (atools_deny-only specialist gets every tool it does not deny, these included); aspawn_taskwithout an agent never does. Each listed extension needs its[tools.*]section enabled, and must appear once. Storage is prepared on the first call in each worker; RAG also loads its embedding model in every worker that calls it.sqlite— queue storage settings used whenbackend = "sqlite"; see[tasks.sqlite].
- Parameters:
enabled (bool)
backend (Literal['rabbitmq', 'sqlite'])
worker_max_steps (Annotated[int, Gt(gt=0)] | Literal['unlimited'])
worker_max_tool_calls (Annotated[int, Gt(gt=0)] | Literal['unlimited'])
delegation_mode (Literal['auto', 'delegate'])
history (bool)
worker_extensions (list[Literal['kv_memory', 'rag', 'codemode']])
sqlite (SqliteTaskQueueConfig)
- class minibot.adapters.config.schema.SqliteTaskQueueConfig(*, sqlite_url='sqlite+aiosqlite:///./data/tasks.db', poll_interval_seconds=5, lease_timeout_seconds=2100, batch_size=4, max_attempts=3, done_retention_seconds=2592000, pool_size=5, echo=False)[source]¶
Queue storage settings for
tasks.backend = "sqlite". TOML section:[tasks.sqlite]sqlite_url— SQLite database URL for the task queue.poll_interval_seconds— how often the consumer checks for queued tasks (default:5).lease_timeout_seconds— lease duration before a stalled task is claimable again (default:2100).batch_size— max tasks leased per poll cycle (default:4).max_attempts— redeliveries before a task is marked failed (default:3). This is queue-level redelivery, distinct from the in-process provider rate-limit retry inapp/tasks/manager.py.done_retention_seconds— how long terminal task rows and their compact event history are kept before purging (default:2592000).
- Parameters:
- class minibot.adapters.config.schema.RabbitMQConsumerConfig(*, broker_url='amqp://guest:guest@localhost:5672/', queue_name='minibot', exchange_name='minibot.tasks', prefetch_count=1)[source]¶
Broker connection settings for
tasks.backend = "rabbitmq". TOML section:[rabbitmq]Requires the
rabbitmqextra:poetry install --extras rabbitmq. Enabling the task system itself lives in[tasks].broker_url— AMQP connection URL (default:"amqp://guest:guest@localhost:5672/").queue_name— queue to consume from (default:"minibot").exchange_name— fanout exchange name (default:"minibot.tasks").prefetch_count— max unacknowledged messages per worker (default:1).
Vault¶
An optional encrypted store for credentials, unlocked once at startup. Needs the vault extra
(poetry install --extras vault). Secrets are written with minibot vault edit (see
CLI Reference) and bound to a destination in configuration — [[tools.mcp.servers]] auth_secret
names a vault entry that the MCP client sends as its own Authorization header. The LLM can
list secret names and nothing more; there is no tool that reads a value and no secret://
substitution it can write into a tool argument. See Security & Sandboxing for the threat model.
- class minibot.adapters.config.schema.VaultConfig(*, enabled=False, path='secrets.vault.yml', password_file='')[source]¶
Encrypted credential vault. TOML section:
[vault]enabled— unlock the vault at startup (default:false).path— the encrypted vault file (default:"secrets.vault.yml"). Write it withminibot vault edit; keep it out of git.password_file— read the unlock password from this file instead of prompting. Supported, but not on equal footing with the interactive prompt: any tool that can read the filesystem can read this file. Same for theMINIBOT_VAULT_PASSWORDenvironment variable.
Secrets are destination-bound: an admin binds a secret to a consumer in config (for example
[[tools.mcp.servers]] auth_secret), and the value is resolved there. The LLM can list secret names and nothing else.
HTTP server¶
An optional HTTP server running on the daemon’s own event loop. Needs the http extra
(poetry install --extras http). It serves /health — open, so a container healthcheck needs
no credential — and an authenticated dashboard at / showing the running model, channels,
extensions, tools, routes, uptime, and pending turns. Its stylesheet and scripts are served beneath
/static, gzip-compressed when the browser accepts it; pages are not compressed, since they
carry tokens next to reflected input such as the search query.
Conversation history is available at /history and can contain sensitive data. The browser chat at /chat
uses the web:1 history session and can contain the same sensitive data. The /history,
/memory and /scheduled pages each have a search box and load more entries as you scroll;
history search matches message text across every conversation. Extensions can
also contribute routes through mb.add_route, or mb.add_page for one with a menu entry (see
Writing extensions). All routes except
/health require a bearer token or HTTP Basic credentials; one is mandatory unless host is
the literal 127.0.0.1 or ::1. When the key-value memory, scheduled prompts, graph and MCP
extensions are enabled, their pages are available at /memory, /scheduled, /graph and
/mcp. When tasks are enabled, /tasks lists the running tasks (?status=all adds finished
ones), shows a task’s prompt, result, error and events, and can cancel a running task. While a reply
runs, the chat’s send button becomes a stop button until you type or attach something; stopping works
like /stop on Telegram. TLS is out of scope: run it behind a reverse proxy when it is reachable
from outside the host. The chat WebSocket authenticates with a
per-boot token sent through Sec-WebSocket-Protocol, not a URL query parameter. A TLS-terminating
proxy must forward the original X-Forwarded-Proto and X-Forwarded-Host headers so browser
origin validation compares against the public origin.
The browser chat can accept image and audio uploads stored temporarily under uploads/temp/web.
This requires [tools.file_storage] enabled; audio additionally requires automatic
transcription. chat_upload_max_attachments, chat_upload_max_image_bytes,
chat_upload_max_audio_bytes and chat_upload_max_total_bytes bound a browser session’s
uploads, and chat_upload_retention_hours controls how long completed uploads are kept before
cleanup (0 disables it).
- class minibot.adapters.config.schema.HTTPServerConfig(*, enabled=False, host='127.0.0.1', port=8080, auth_token='', basic_auth_user='', basic_auth_password='', chat_upload_max_attachments=3, chat_upload_max_image_bytes=5000000, chat_upload_max_audio_bytes=10000000, chat_upload_max_total_bytes=12000000, chat_upload_retention_hours=24)[source]¶
Built-in HTTP server. TOML section:
[http]enabled— serve HTTP alongside the daemon (default:false). Needs thehttpextra.host/port— where to bind (default:127.0.0.1:8080); port0picks a free one. Only literal127.0.0.1and::1can run without credentials.auth_token— bearer token required on every route except/health. Accepts${secret:NAME}.basic_auth_user/basic_auth_password— HTTP Basic credentials, used instead of the bearer token when set (browsers prompt for these natively). Accepts${secret:NAME}.chat_upload_*— browser-chat media limits and temporary upload retention. Uploads require[tools.file_storage] enabled; audio also requires automatic transcription.
Routes come from core features and from extensions calling
mb.add_routeormb.add_page. There is no TLS here: put a reverse proxy in front when this is reachable from outside the host.Static dashboard assets under
/staticare served withCache-Control: no-storewhen[runtime].environmentis"development"or"debug", and with normal caching otherwise. They are gzip-compressed when the browser accepts it; pages are not.- Parameters:
enabled (bool)
host (str)
port (int)
auth_token (str)
basic_auth_user (str)
basic_auth_password (str)
chat_upload_max_image_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
chat_upload_max_audio_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
chat_upload_max_total_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
chat_upload_retention_hours (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0)])])
Extensions¶
Python extensions loaded at startup. See Writing extensions for the full extension API.
The optional relation graph is a built-in extension. After installing the graph extra, enable
minibot.extensions.tools.graph in [extensions].modules. Its optional configuration accepts
sqlite_url (default sqlite+aiosqlite:///./data/graph.db) and echo for SQL logging. See
Relation graph.
- class minibot.adapters.config.schema.ExtensionsConfig(*, modules=<factory>, config=<factory>)[source]¶
Python extensions loaded at startup. TOML section:
[extensions]modules— importable module names, each exposing aregister(mb)function. Resolved via normal Python import, so both pip-installed packages and local modules onPYTHONPATHwork.config— per-extension settings, keyed by extension name. The extension receives its own slice asmb.config; keys inside are arbitrary.
A module that cannot be imported, has no
register, or whoseregisterraises fails startup: a silently missing tool is worse than a crash on boot.
Tool Configuration¶
Section |
Config model |
Key options |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note
Every owner-scoped tool — key/value memory, the relation graph, scheduled jobs and the RAG
corpus — is owned by [runtime].owner_id (default "primary"). MiniBot assists exactly
one person, so that value is a constant of the deployment and is never derived from a message
or a task payload.
Conversation history is scoped separately, per channel and chat. One owner, many chat sessions — a private Telegram chat, a group and the console do not share history, but they do share that owner’s memory, graph and schedules.
Multi-user or multi-tenant isolation is deliberately not supported; it would arrive as an opt-in extension with its own identity model, not by reusing a channel’s sender id.
Tool Config Models¶
- class minibot.adapters.config.schema.KeyValueMemoryConfig(*, enabled=False, sqlite_url='sqlite+aiosqlite:///./data/kv_memory.db', pool_size=5, echo=False, default_limit=20, max_limit=100)[source]¶
Key/value memory tool settings. TOML section:
[tools.kv_memory]enabled— expose thememory_*tools (default:false).sqlite_url— SQLite database URL for stored entries.pool_size/echo— SQLAlchemy engine settings.default_limit/max_limit— page size and hard cap for memory searches.
Entries are owned by
[runtime].owner_id, which is shared with every other owner-scoped tool rather than configured here.
- class minibot.adapters.config.schema.HTTPClientToolConfig(*, enabled=False, timeout_seconds=10, follow_redirects=False, max_redirects=5, max_bytes=16384, max_parse_bytes=2000000, response_processing_mode='auto', max_chars=None, normalize_whitespace=True, spill_to_managed_file=False, spill_after_chars=16000, spill_preview_chars=2000, max_spill_bytes=5000000, spill_subdir='http_responses/tmp')[source]¶
- Parameters:
enabled (bool)
follow_redirects (bool)
max_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
max_parse_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
response_processing_mode (Literal['none', 'auto', 'text', 'compact'])
normalize_whitespace (bool)
spill_to_managed_file (bool)
max_spill_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
spill_subdir (str)
- class minibot.adapters.config.schema.TimeToolConfig(*, enabled=True, default_format='%Y-%m-%dT%H:%M:%SZ')[source]¶
- class minibot.adapters.config.schema.WaitToolConfig(*, enabled=False, max_milliseconds=30000)[source]¶
- class minibot.adapters.config.schema.CalculatorToolConfig(*, enabled=True, default_scale=28, max_expression_length=200, max_exponent_abs=1000)[source]¶
- class minibot.adapters.config.schema.PythonExecToolConfig(*, enabled=True, backend='host', python_path=None, venv_path=None, sandbox_mode='basic', default_timeout_seconds=8, max_timeout_seconds=20, max_output_bytes=64000, max_code_bytes=32000, artifacts_enabled=True, artifacts_default_subdir='generated', artifacts_allowed_extensions=<factory>, artifacts_max_files=5, artifacts_max_file_bytes=5000000, artifacts_max_total_bytes=20000000, artifacts_allow_in_jail=False, artifacts_jail_shared_dir=None, pass_parent_env=False, env_allowlist=<factory>, rlimit=PythonExecRLimitConfig(enabled=False, cpu_seconds=2, memory_mb=256, fsize_mb=16, nproc=64, nofile=256), cgroup=PythonExecCgroupConfig(enabled=False, driver='systemd', cpu_quota_percent=100, memory_max_mb=256), jail=PythonExecJailConfig(enabled=False, command_prefix=[]))[source]¶
- Parameters:
enabled (bool)
backend (Literal['host'])
python_path (str | None)
venv_path (str | None)
sandbox_mode (Literal['none', 'basic', 'rlimit', 'cgroup', 'jail'])
max_output_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
max_code_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
artifacts_enabled (bool)
artifacts_default_subdir (str)
artifacts_max_file_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
artifacts_max_total_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
artifacts_allow_in_jail (bool)
artifacts_jail_shared_dir (str | None)
pass_parent_env (bool)
rlimit (PythonExecRLimitConfig)
cgroup (PythonExecCgroupConfig)
jail (PythonExecJailConfig)
- class minibot.adapters.config.schema.PythonExecRLimitConfig(*, enabled=False, cpu_seconds=2, memory_mb=256, fsize_mb=16, nproc=64, nofile=256)[source]¶
- class minibot.adapters.config.schema.PythonExecCgroupConfig(*, enabled=False, driver='systemd', cpu_quota_percent=100, memory_max_mb=256)[source]¶
- class minibot.adapters.config.schema.PythonExecJailConfig(*, enabled=False, command_prefix=<factory>)[source]¶
- class minibot.adapters.config.schema.BashToolConfig(*, enabled=False, default_timeout_seconds=15, max_timeout_seconds=120, max_output_bytes=128000, pass_parent_env=False, env_allowlist=<factory>, spill_to_managed_file=False, spill_after_chars=2000, spill_preview_chars=500, spill_subdir='bash_output/tmp')[source]¶
- Parameters:
- class minibot.adapters.config.schema.ToolOutputSpillConfig(*, enabled=True, spill_after_chars=8000, preview_chars=800, subdir='tool_output/tmp', exclude_tools=<factory>)[source]¶
Generic offload of oversized tool results to a managed temp file.
Applies to every tool except those listed in
exclude_tools, and only when file storage is enabled and the agent also has a read-back tool available (grep,code_read,read_fileorbash).activate_skillspills only past 64,000 characters (orspill_after_chars, if larger), so skill instructions normally arrive inline.
- class minibot.adapters.config.schema.ApplyPatchToolConfig(*, enabled=False, restrict_to_workspace=True, workspace_root='.', allow_outside_workspace=False, preserve_trailing_newline=True, max_patch_bytes=262144)[source]¶
- Parameters:
enabled (bool)
restrict_to_workspace (bool)
workspace_root (str)
allow_outside_workspace (bool)
preserve_trailing_newline (bool)
max_patch_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
- class minibot.adapters.config.schema.FileStorageToolConfig(*, enabled=False, root_dir='./data/files', max_write_bytes=64000, allow_outside_root=False, save_incoming_uploads=False, uploads_subdir='uploads', incoming_temp_subdir='uploads/temp')[source]¶
- Parameters:
enabled (bool)
root_dir (str)
max_write_bytes (Annotated[int, BeforeValidator(func=~minibot.config.schema._coerce_byte_size, json_schema_input_type=PydanticUndefined), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])])
allow_outside_root (bool)
save_incoming_uploads (bool)
uploads_subdir (str)
incoming_temp_subdir (str)
- class minibot.adapters.config.schema.GrepToolConfig(*, enabled=False, max_matches=200, max_file_size_bytes=1000000)[source]¶
- class minibot.adapters.config.schema.BrowserToolConfig(*, output_dir='./data/files/browser')[source]¶
- Parameters:
output_dir (str)
- class minibot.adapters.config.schema.AudioTranscriptionToolConfig(*, enabled=False, model='small', device='auto', compute_type='int8', beam_size=5, vad_filter=True, auto_transcribe_short_incoming=True, auto_transcribe_max_duration_seconds=45, server_url=None)[source]¶
- Parameters:
enabled (bool)
model (str)
device (Literal['auto', 'cpu', 'cuda'])
compute_type (str)
vad_filter (bool)
auto_transcribe_short_incoming (bool)
auto_transcribe_max_duration_seconds (Annotated[int, Gt(gt=0)])
server_url (Annotated[str, AfterValidator(func=~minibot.config.schema._check_http_url)] | None)
- class minibot.adapters.config.schema.MCPToolConfig(*, enabled=False, name_prefix='mcp', timeout_seconds=10, reload=False, servers=<factory>)[source]¶
- class minibot.adapters.config.schema.MCPServerConfig(*, name, mode='bridge', transport='stdio', command=None, args=<factory>, env=<factory>, cwd=None, url=None, headers=<factory>, auth_secret=None, enabled_tools=<factory>, disabled_tools=<factory>, catalog_cache_ttl_seconds=60)[source]¶
- class minibot.adapters.config.schema.SkillsToolConfig(*, enabled=True, paths=<factory>, preload_catalog=True, native=True, native_disabled=<factory>, write_path='~/.minibot/skills', install=False)[source]¶
Skill discovery for the main agent.
preload_catalogembeds skill names and descriptions in the system prompt. Without it the prompt only tells the model to calllist_skills, so it cannot tell whether a relevant skill exists — while the specialist roster is in the prompt, which makes delegating look like the obvious route even for work a skill covers.Skills are discovered from three sources, in precedence order: project (
./.minibot/skillsand./.agents/skills, orpathswhen set), user (the same directory names under$HOME), and native — the skills bundled inside the MiniBot package. Settingpathsreplaces the project and user lists;nativeis independent of it, so configuringpathsnever silently drops the bundled skills.native_disabledopts out of individual bundled skills by name, and is the only way to switch one off: bundled skills carry noenabledfrontmatter, because nobody edits files inside an installed package. A project- or user-level skill of the same name always wins over a bundled one, which is the supported way to override one rather than disable it.write_pathis where the agent creates or installs skills, and is added as the highest-priority discovery path so a skill written at runtime is found without a restart. It defaults to~/.minibot/skills, so it outranks the project-level./.minibot/skills.installattaches theinstall_skilltool, which downloads skills from GitHub or an archive URL intowrite_path. It is off by default because an installed skill is third-party instructions the agent will later follow; while it is off, the bundledinstall-skillskill is hidden as well.
- class minibot.adapters.config.schema.RagToolConfig(*, enabled=False, backend='sqlite', sqlite_url='sqlite+aiosqlite:///./data/rag.db', echo=False, qdrant_url='http://localhost:6333', collection_name='minibot_chunks', embedding=RagEmbeddingConfig(model='sentence-transformers/all-MiniLM-L12-v2', dim=384, truncate_dim=None, max_sequence_tokens=128), rerank=RagRerankConfig(enabled=False, model='cross-encoder/ms-marco-MiniLM-L2-v2', candidate_limit=50, max_results=7), chunk_size_tokens=96, chunk_overlap_tokens=20, search_limit=5, truncate_result_tokens=False, max_result_tokens=1500)[source]¶
Retrieval-augmented generation settings. TOML section:
[tools.rag]backend— vector store:"sqlite"(default; no service required) or"qdrant".sqlite_url— SQLite database URL used whenbackend = "sqlite".qdrant_url— Qdrant HTTP endpoint used whenbackend = "qdrant".
The SQLite backend scores with an exact scan over the rows left after the scope filters, so it trades throughput at very large corpora for exact recall under filtering; Qdrant keeps an approximate index and is the option to reach for when the corpus outgrows that.