jeevesagent.tools.builtin

Built-in tools for filesystem + shell agents.

Four tools that any Agent can register to gain the canonical “Claude-Code-shaped” capability set:

All four are factory functions: they take a workdir (and in the case of bash_tool a few safety knobs) and return a ready-to-register Tool instance. The closure captures the workdir, so the resulting tool is workdir-scoped — the model can’t escape it via ../ traversal or absolute paths.

Usage:

from jeevesagent import (
    Agent, read_tool, write_tool, edit_tool, bash_tool,
)

agent = Agent(
    "You are a research agent.",
    model="gpt-4.1-mini",
    tools=[
        read_tool(workdir="/tmp/agent_work"),
        write_tool(workdir="/tmp/agent_work"),
        edit_tool(workdir="/tmp/agent_work"),
        bash_tool(workdir="/tmp/agent_work", timeout=30.0),
    ],
)

Or as a bundle:

from jeevesagent import filesystem_tools, bash_tool

agent = Agent(
    "...",
    model="...",
    tools=filesystem_tools("/tmp/agent_work") + [bash_tool("/tmp/agent_work")],
)

Safety

  • Workdir-scoped by default. Read / write / edit refuse paths that resolve outside the workdir. bash_tool runs commands with the workdir as cwd.

  • Timeout on bash. Default 30 seconds; override via the timeout kwarg. Commands that exceed the timeout are killed.

  • Destructive-command denylist. bash_tool rejects a small set of obviously-dangerous patterns (rm -rf /, sudo, mkfs, etc.) by default. Override via the allow_pattern callable for advanced use.

  • Edit requires unique match. edit_tool’s old_string must appear EXACTLY once in the file (unless replace_all=True is passed in the call) — forces the model to provide enough context for unambiguous edits, the same approach Claude Code takes.

These will be the foundation of the upcoming Deep Agent architecture (planner + filesystem state + subagent registry).

Exceptions

PathEscapeError

Raised when a tool argument resolves outside its workdir.

Functions

bash_tool(→ jeevesagent.tools.registry.Tool)

Build a Tool that runs a shell command with the

default_workdir(→ pathlib.Path)

Return the framework's default workdir for built-in tools,

edit_tool(→ jeevesagent.tools.registry.Tool)

Build a Tool that does find-and-replace inside an

filesystem_tools(→ list[jeevesagent.tools.registry.Tool])

Return all three filesystem tools (read + write + edit)

read_tool(→ jeevesagent.tools.registry.Tool)

Build a Tool that reads a text file under workdir.

write_tool(→ jeevesagent.tools.registry.Tool)

Build a Tool that writes / overwrites a text file

Module Contents

exception jeevesagent.tools.builtin.PathEscapeError[source]

Bases: ValueError

Raised when a tool argument resolves outside its workdir.

Initialize self. See help(type(self)) for accurate signature.

jeevesagent.tools.builtin.bash_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'bash', timeout: float = 30.0, allow_pattern: collections.abc.Callable[[str], bool] | None = None, extra_env: dict[str, str] | None = None) jeevesagent.tools.registry.Tool[source]

Build a Tool that runs a shell command with the workdir as the current working directory.

Default safety:

  • Commands matching the built-in destructive patterns (rm -rf /, sudo, mkfs, fork bombs, …) are rejected before being executed.

  • Commands run with a default timeout of 30 seconds; the subprocess is killed on timeout.

  • The shell is invoked via /bin/sh -c <command>, so pipelines + redirections work the way you’d expect.

Knobs:

  • allow_pattern — a callable that takes the command string and returns True if the command should run. When provided, it OVERRIDES the default deny list — you take full responsibility.

  • extra_env — extra environment variables merged into the subprocess env.

  • timeout — seconds before the command is killed.

workdir is optional; None uses the framework’s default tempdir (shared with the other built-in tools).

jeevesagent.tools.builtin.default_workdir() pathlib.Path[source]

Return the framework’s default workdir for built-in tools, creating it lazily on first call.

The directory is a fresh tempdir under $TMPDIR/jeeves_agent_*, created once per process. All built-in tool factories share it when called without an explicit workdir argument, so an Agent that registers read_tool() and write_tool() (no args) sees the same place.

The directory is NOT auto-cleaned at process exit — leave that to the OS’s tempdir cleanup so debug data survives a crash.

jeevesagent.tools.builtin.edit_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'edit') jeevesagent.tools.registry.Tool[source]

Build a Tool that does find-and-replace inside an existing file under workdir.

The tool’s signature seen by the model:
``edit(path: str, old_string: str, new_string: str,

replace_all: bool = False)``

Behaviour matches Claude Code’s Edit tool:

  • old_string must be EXACTLY present in the file. Mismatch (whitespace, indentation, line breaks) → error.

  • old_string must appear EXACTLY once in the file unless replace_all=True is passed — forces the model to give enough surrounding context for unambiguous matches.

  • new_string replaces old_string (or every occurrence if replace_all=True).

workdir is optional; None uses the framework’s default tempdir (shared with the other built-in tools).

jeevesagent.tools.builtin.filesystem_tools(workdir: pathlib.Path | str | None = None) list[jeevesagent.tools.registry.Tool][source]

Return all three filesystem tools (read + write + edit) bound to a single workdir. bash_tool is excluded — pair them only when you want shell access too.

workdir is optional; None uses the framework’s default tempdir (shared with bash_tool() called the same way).

jeevesagent.tools.builtin.read_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'read', line_limit: int = _DEFAULT_READ_LINE_LIMIT) jeevesagent.tools.registry.Tool[source]

Build a Tool that reads a text file under workdir.

The tool’s signature seen by the model:

read(path: str, offset: int = 0, limit: int | None = None)

Returns the file’s text with line numbers prefixed (one line per output line), in the same format Claude Code’s Read tool uses — that lets the edit tool work without ambiguity later. Long files are truncated to line_limit lines per call; pass offset / limit to read further chunks.

Errors (file-not-found, path-escape) are returned as a string starting with "ERROR: " rather than raising — the model sees them as a tool result and can adjust.

workdir is optional; None uses the framework’s default tempdir (shared with the other built-in tools called without a workdir).

jeevesagent.tools.builtin.write_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'write', create_parents: bool = True) jeevesagent.tools.registry.Tool[source]

Build a Tool that writes / overwrites a text file under workdir.

The tool’s signature seen by the model:

write(path: str, content: str)

Overwrites existing files. With create_parents=True (the default), missing parent directories are created automatically.

Returns a confirmation string with the byte count, or an "ERROR: "-prefixed message on failure.

workdir is optional; None uses the framework’s default tempdir (shared with the other built-in tools).