Built-in tools
This page lists the tools and commands included with the Bub application. Model-facing names use underscores, so fs.read becomes fs_read; text commands keep the original name, as in ,fs.read path=README.md. Plugins can add or replace tools, so use ,help, ,tool.describe, and bub hooks to inspect the actual collection.
Arguments can be positional or use key=value. Write booleans as true / false and lists as JSON strings. Quote values containing spaces:
,bash command='printf "hello world"' timeout_seconds=10,tool.describe names='["web.fetch", "tape.search"]'File, Shell, and code mode operations use the session’s Environment. By default, they run on the host, not in a security sandbox.
Processes
Section titled “Processes”bash(command, cwd=None, timeout_seconds=30, background=False)| Parameter | Description |
|---|---|
command |
Shell command to execute |
cwd |
Working directory; relative paths are resolved by the current environment |
timeout_seconds |
Time to wait in the foreground; on timeout, the command continues in the background and returns a shell_id |
background |
Start in the background immediately; this mode ignores timeout_seconds |
A non-zero exit status produces a tool error and preserves the output. An empty foreground result returns (no output).
bash.output
Section titled “bash.output”bash.output(shell_id, offset=0, limit=None)Read the output buffer by character position. offset is clamped between 0 and the current output length; limit bounds the characters returned. The result includes status, exit_code, output, and next_offset for the following read. exit_code is null while the process is running.
,bash.output shell_id=bsh-12345678 offset=0 limit=4000bash.kill
Section titled “bash.kill”bash.kill(shell_id)Terminate a background process and return its final status and exit code. You can also call it after the process has finished to read its status.
fs.read
Section titled “fs.read”fs.read(path, offset=0, limit=None)Read UTF-8 text. offset is a zero-based line number and limit is the maximum number of lines, not characters. Omit both to return the whole file.
.png, .jpg, .jpeg, .webp and .gif files are returned as an image the model can see, up to 10 MiB; offset and limit are ignored for them.
fs.write
Section titled “fs.write”fs.write(path, content)Write the complete text and create missing parent directories. Existing files are overwritten; this operation does not append or apply patches.
fs.edit
Section titled “fs.edit”fs.edit(path, old, new, start=0)Search from the zero-based start line and replace every old occurrence in that range with new. A missing match produces an error. This is literal text replacement, not syntax-aware code editing.
web.fetch
Section titled “web.fetch”web.fetch(url, headers=None, timeout=10)Make an HTTP GET request. The default Accept: text/markdown header can be overridden through headers. timeout is the total request time in seconds. The result contains the final url, HTTP status, content_type, and response body; unsuccessful statuses raise tool errors. It fetches one URL and does not provide web search, crawling, or browser interaction.
Skill and tool discovery
Section titled “Skill and tool discovery”skill(name=None)Without name, list skills available in the current context. With a name, return its location and body. A missing skill returns (no such skill) without calling a model.
tool.describe
Section titled “tool.describe”tool.describe(names)names is a list of runtime names or model aliases. The result contains each tool’s name, description, and parameter schema, with unrecognized names under unknown. Deferred definitions become callable from the next Step, and later Turns offer loaded tools directly until the Tape is reset. Loading is recorded in the current Tape. Requesting a tool that is already loaded returns its name under already_loaded instead of its definition.
tape.info
Section titled “tape.info”No parameters. Return the Tape name, entry and anchor counts, latest anchor, entries after it, and the latest recorded token usage and cache hit rate. The last two values are None when the provider does not return those details.
tape.search
Section titled “tape.search”tape.search(query, limit=20, start=None, end=None, kinds=["message", "tool_result"])| Parameter | Description |
|---|---|
query |
Case-insensitive substring match against the JSON text of entry kind, date, payload, and meta |
limit |
Maximum number of matching entries to read |
start / end |
Optional inclusive ISO date or time bounds |
kinds |
Entry kinds: message, tool_call, tool_result, event, anchor, system, or error |
The result contains matches and a filtered count. Output produced by the search command itself is filtered so repeated searches do not recursively include earlier results.
The builtin application uses FuzzyFileTapeStore: text searches walk backward from the newest entry, deduplicate identical content, and try fuzzy matches in addition to direct substrings. Queries shorter than three characters use substring matching only. kinds and dates are filtered first, then limit applies to the deduplicated results. Other Tape backends define their own fuzzy matching and ordering.
tape.anchors
Section titled “tape.anchors”No parameters. List anchor names in recording order, or return (no anchors).
tape.handoff
Section titled “tape.handoff”tape.handoff(name="handoff", summary="")Write an anchor that moves the default context start to this point. Earlier entries remain in storage. Bub does not generate a summary. The default context reads after the anchor, so the boundary anchor itself does not enter model input; when reading with anchor=None, the default selector converts it into the assistant message [Anchor created: <name>]: <state JSON>.
tape.reset
Section titled “tape.reset”tape.reset(archive=false)Clear the current Tape. With archive=true, first archive the main Tape and sidecars into the backup directory under Bub home’s tapes/; the default does not archive. Reset creates a new session/start anchor and does not automatically restore backups.
Subagents
Section titled “Subagents”subagent
Section titled “subagent”subagent(prompt, agent=None, description=None, model=None, reasoning_effort=None, session="temp", allowed_tools=None, allowed_skills=None, timeout_seconds=None)| Parameter | Type and default | Description |
|---|---|---|
prompt |
String or content-block list, required | The task for the subagent |
agent |
str | None, default None |
Name of a subagent profile; its settings apply unless given here |
description |
str | None, default None |
Short label shown in task lists; defaults to the prompt’s first line |
model |
str | None, default None |
provider:model for the child run; by default, the profile’s model or the current configuration |
reasoning_effort |
str | None, default None |
Reasoning effort of the child’s model |
session |
str, default "temp" |
Session strategy for the subagent |
allowed_tools |
list[str] | None, default None |
Tools the subagent may use; by default, the parent run’s tools |
allowed_skills |
list[str] | None, default None |
Skills the subagent may use; by default, the parent run’s skills |
timeout_seconds |
int | None, default None |
Stop the child after this many seconds; defaults to the profile’s limit, then BUB_SUBAGENT_TIMEOUT_SECONDS |
Tool names can be runtime names or model aliases, so both fs.read and fs_read work. A child never gets more than its parent: the tools default to the parent run’s allowed tools, and naming a tool outside them is an error. Skill names are case-insensitive and are likewise narrowed to the parent’s; allowed_skills=[] disables skill discovery. Children cannot start or manage subagents, so subagent and subagent.* are always excluded.
session determines what the subagent starts from and what is kept:
| Value | Behavior |
|---|---|
temp |
Creates an empty temp/<task_id> session that is discarded afterwards |
fork |
Starts from a copy of the current conversation, including the current turn so far, and is discarded afterwards; inherit is accepted as an alias |
| Any other string | Uses that session ID; the session is kept and can be continued later with subagent.send |
A fork copies the parent’s Tape when the call is made, so the child does not see what the parent records later. Each child gets its own turn state: a shallow copy of the parent’s without the parent’s steering key, tool scope or running Tape, so messages steered into the parent stay with the parent.
The tool returns:
{ "task_id": "sub-1a2b3c4d", "session_id": "temp/sub-1a2b3c4d", "status": "completed", # queued, running, completed, failed or cancelled "output": "...", "errors": [],}The subagent runs in the background: the tool returns at once with status queued or running and an empty output. When the task ends, the parent receives its result as a message:
<subagent-notification task_id="sub-1a2b3c4d" status="completed">...</subagent-notification>output is the child’s final answer: the text of its last step that made no tool calls. A run that started subagents does not end while their results are outstanding: when a step ends without tool calls, the run waits for the next task to finish and continues with its notification. A task that ends after its run, because the run failed, was cancelled or reached max_steps, reports through the session’s steering inbox: before the next step of a running turn, or with the next turn. Results read with subagent.wait send no notification.
At most BUB_SUBAGENT_MAX_CONCURRENCY subagents run at once in the process; later ones wait with status queued. Ending the runtime, or the quit command for a session, cancels running subagents. When a temp or fork subagent ends, the background shells it started are stopped.
subagent.wait
Section titled “subagent.wait”subagent.wait(task_ids=None, timeout_seconds=60)Return task results, waiting up to timeout_seconds for the tasks to finish; 0 checks without waiting. Without task_ids, it covers the session’s tasks whose results were not read yet. Tasks still running after the timeout are returned with their current status.
subagent.send
Section titled “subagent.send”subagent.send(task_id, message)Send a message to a task. A running task receives it before its next step. A finished task with a named session continues that conversation as a new background task, bounded by the current run’s tools; a finished temp or fork task cannot be continued.
subagent.cancel
Section titled “subagent.cancel”subagent.cancel(task_id)Stop a running task. Its status becomes cancelled, and any partial output is kept.
Events
Section titled “Events”Every task publishes events on the agent’s event bus (agent.events), with the starting run’s turn state as state:
| Event | When |
|---|---|
SubagentStarted |
The task left the queue and its child run is starting |
SubagentOutput |
The child run produced a stream event, available as event |
SubagentFinished |
The task ended as completed, failed or cancelled |
Each carries task, a SubagentTask with task_id, session_id, parent_session_id, parent_call_id, description, model, profile, status, output, errors, usage, step and tool-call counts, and timestamps. They are importable from bub.builtin.subagents. A failing handler is logged and does not fail the child run.
The subagent shares its BubAgent, plugin hooks, model credentials, workspace, Environment, files, and external systems with the parent Agent. A session strategy isolates conversation records and model context, not file, network, or process side effects. To isolate execution, provide a separate execution environment; for approval, use tool interception. In chat, the model generates the arguments, so allowed_tools is not a security boundary on its own.
See Subagents for complete usage examples.
Code mode and large results
Section titled “Code mode and large results”run_code
Section titled “run_code”run_code(code, timeout_seconds=120)Execute Python in code mode. Call tools with await tools.<name>(key=value) using keyword arguments. Only printed output and explicitly attached media enter the result. See Custom tools and code mode for complete usage.
attach_image / attach_audio / attach_video
Section titled “attach_image / attach_audio / attach_video”attach_image(image)attach_audio(audio)attach_video(video)Attach an image, audio clip, or video to the current run_code result. The corresponding parameter accepts a republic.Image, republic.Audio, or republic.Video object, or a string containing a file path in the current Environment, an http(s) or gs URL, or a data URL. Local files are resolved and read through the current environment.
These tools have exposure="code" and can only be called inside run_code. Each call appends the media to the result and returns a confirmation string.
spill.read
Section titled “spill.read”spill.read(handle, cursor=0, count=1, from_end=false)Read a large tool result moved into the spill sidecar. handle comes from the original tool output, cursor is the chunk position, and count can read at most four chunks per call. With from_end=true, read backward from the end. Use next_cursor for the following page; complete=true marks the boundary in that direction.
New spills are created only when the current model tools include spill.read. Configure the threshold with BUB_SPILL_THRESHOLD. Setting it to 0 stops new spills but leaves existing results readable.
Results above the threshold are written to the spill sidecar, and the main conversation keeps only a preview and a handle. A sidecar is a separate Tape paired with the session Tape: it forks, merges, and resets with it, but its entries never enter the main context. See Sidecars for the concept.
Session commands
Section titled “Session commands”These tools have exposure="command": they are available as text commands and are not exposed directly to the model.
| Command | Parameters | Purpose |
|---|---|---|
,help |
None | List internal commands and the command prefix |
,model |
model_id |
Switch the session model from the next Turn and record it in the Tape |
,reasoning_effort |
reasoning_effort |
Set session reasoning effort; supported values depend on the provider |
,code_mode |
enable=true / false |
Toggle session code mode from the next Turn |
,quit |
None | Clear pending messages, running tasks, and background Shell processes for the session |
Session settings are recorded in the Tape and restored after a restart. They are not BUB_* environment variables; see Settings for configuration precedence.