Using the terminal · 08
Tools
riffer-rig ships six tools: read, write, edit, bash, and bash’s two background-job companions, bash_output and bash_kill. Each comes from a bundled extension, so you can leave any of them out or replace it.
The six tools
| Tool | Does | Guidance in its description |
|---|---|---|
read |
returns a file with line numbers; offset and limit (default 2,000 lines) read a slice |
“Read a file before editing it, and read rather than guess what a file contains.” |
write |
creates or overwrites a file, making parent directories | “Use this for new files or full rewrites; prefer edit for changes to an existing file.” |
edit |
replaces an exact string, which must be unique unless replace_all is set |
“Prefer this over write for existing files; pass enough surrounding text to make old_string unique.” |
bash |
runs a shell command in the working directory and returns its combined output (limits) | “Use this for exploring and running things: ls, rg or grep, find, tests, git, package managers.” |
bash_output |
returns what a background job has written since the last read | “Use this to check on a server, watcher or long-running job before it finishes.” |
bash_kill |
stops a background job and everything it started | “Use this to stop a job that is no longer needed rather than leaving it running.” |
The guidance sentence is the only coding advice the model gets. The base prompt names no tools, so the advice disappears with the tool when you leave it out.
A bad argument, a missing file or a failing command never crashes a turn: it comes back to the model as a tool error it can read and act on. Relative paths resolve against the session’s working directory.
bash limits
| Limit | Value |
|---|---|
| timeout | timeout_ms, default 120,000 ms per call |
| output | truncated at 30,000 bytes |
| non-zero exit | a tool error |
| returns | when the command exits, without waiting for anything it left running in the background |
| on cancel | the command and everything it started are killed, and the call returns [cancelled] |
What runs without asking
riffer-rig has no permission prompts for tool calls. Every call the model makes — to these six tools, to MCP tools and to tools from extensions — runs as soon as the model makes it, with your user’s access to the files and shell.
riffer-rig asks only before:
- loading a project’s
.riffer/rig.rbfor the first time (trust); - installing a provider’s SDK gem;
- storing a credential you type.
To narrow what the model can do:
| To… | Use |
|---|---|
| allow only some tools for one run | --tools read,edit (Identifiers) |
| never load some tools | "extensions": {"disabled": ["bash", "write"]} in settings |
| refuse certain calls | a before_tool_call hook that returns :block |
| run commands somewhere safer | replace bash with your own |
Background jobs
bash with run_in_background starts the command as a job and returns a job id and an output file path at once. The job keeps running after the call, and after the turn. Its stdin is /dev/null, and everything it writes goes to the output file, which keeps the most recent output once it passes 10 MB.
The model never has to poll for the exit: riffer-rig tells it, with a notice holding the command, the exit status (or the signal that killed it), the duration, the output path and the last 2,000 bytes of output:
<background-job id="a1b2c3d4" status="completed" exit-code="0" duration="12.3s">
<command>make build</command>
<output-path>/tmp/jobs/a1b2c3d4</output-path>
<output-tail>
compiled
done
</output-tail>
</background-job>
When the model hears depends on what the session is doing when the job exits:
a turn running idle the next turn
|=============================| |----------------| |============
^ ^ ^
1 2 3
| The job exits… | The model hears… |
|---|---|
| 1. during a turn | in the next tool result it reads |
| 2. as the reply finishes | at once: the turn continues with the notice, up to 5 times per prompt |
| 3. between turns | at the next prompt, or in a turn the host starts for it |
What the host does at 3:
| Host | When a job exits between turns |
|---|---|
| terminal | starts a turn by itself, if it is waiting for your input |
| headless | waits for running jobs before exiting, giving the model a turn for each exit |
| ACP | shows the exit to the client at once; the model hears at the next prompt |
| your app | decides for itself (Embedding) |
Cancelling a turn does not stop its jobs. bash_kill stops one; ending the session stops them all and deletes their output files. Once the model has been told of an exit, bash_output and bash_kill no longer know the job, and the rest of its output is in the file named in the notice.
Identifiers
A tool’s name everywhere — --tools, the tools: allowlist when embedding, a replacement — is its riffer identifier: read, write, edit, bash, bash_output, bash_kill, or the identifier an extension tool declares.
The allowlist names tools, not extensions: --tools bash gives the model bash without bash_output and bash_kill, so it can start background jobs but not follow or stop them. A name that matches no tool is reported, so a typo doesn’t silently drop a tool. The allowlist does not apply to MCP tools.
Provider-native tools
Web search runs inside the provider’s own request, so it is part of the core rather than an extension. tools.native switches it on. anthropic, openai and azure_openai support it, and a /model switch turns the tool on or off to match the new provider.
Replacing a tool
Register a tool with the same identifier from a later extension:
class SandboxedBash < Riffer::Tool
identifier 'bash'
description 'Run a shell command inside the sandbox.'
params do
required :command, String, description: 'The command to run'
end
def call(context:, command:)
output, status = Sandbox.run(command)
status.success? ? text(output) : error(output, type: :execution_error)
end
end
Riffer::Rig.extension('sandbox') { |rig| rig.tool SandboxedBash }
The later registration takes the earlier tool’s place. Replacement is per identifier: a replaced bash leaves bash_output and bash_kill registered, acting on the jobs in the session’s job registry, where a replacement can register its own.