Skip to content

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.

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(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=4000
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(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(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(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(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(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(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.

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(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.

No parameters. List anchor names in recording order, or return (no anchors).

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(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.

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(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(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(task_id)

Stop a running task. Its status becomes cancelled, and any partial output is kept.

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.

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(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.

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.