jeevesagent.tools

Tool registry and decorators + built-in filesystem / shell tools.

Users typically construct tools via tool() (decorator) and pass the resulting Tool objects to Agent. The agent wraps them in an InProcessToolHost.

For the canonical “Claude-Code-shaped” tool set (read / write / edit / bash), import the four factory functions from jeevesagent.tools.builtin (also re-exported at the top level).

Submodules

Exceptions

PathEscapeError

Raised when a tool argument resolves outside its workdir.

Classes

InProcessToolHost

A dict-backed ToolHost.

Tool

A registered tool: definition plus the callable that executes it.

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.

tool(…)

Promote a callable to a Tool.

write_tool(→ jeevesagent.tools.registry.Tool)

Build a Tool that writes / overwrites a text file

Package Contents

exception jeevesagent.tools.PathEscapeError[source]

Bases: ValueError

Raised when a tool argument resolves outside its workdir.

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

class jeevesagent.tools.InProcessToolHost(tools: list[Tool | collections.abc.Callable[Ellipsis, Any]] | None = None)[source]

A dict-backed ToolHost.

async call(tool: str, args: collections.abc.Mapping[str, Any], *, call_id: str = '') jeevesagent.core.types.ToolResult[source]
get(name: str) Tool | None[source]
async list_tools(*, query: str | None = None) list[jeevesagent.core.types.ToolDef][source]
register(item: Tool | collections.abc.Callable[Ellipsis, Any]) Tool[source]
unregister(name: str) bool[source]

Remove a tool by name. Returns True if removed.

async watch() collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent][source]

In-process registry is static; the generator yields nothing.

Iterating over an empty tuple keeps this an async generator (so the return type is AsyncIterator) without ever producing an event at runtime.

class jeevesagent.tools.Tool[source]

A registered tool: definition plus the callable that executes it.

async execute(args: collections.abc.Mapping[str, Any]) Any[source]

Invoke the underlying callable.

Async functions are awaited; sync functions are dispatched to a worker thread via anyio.to_thread.run_sync() so they don’t block the event loop.

to_def() jeevesagent.core.types.ToolDef[source]
description: str
destructive: bool = False
fn: collections.abc.Callable[Ellipsis, Any]
input_schema: dict[str, Any]
name: str
jeevesagent.tools.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.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.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.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.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.tool(fn: collections.abc.Callable[Ellipsis, Any]) Tool[source]
jeevesagent.tools.tool(*, name: str | None = None, description: str | None = None, destructive: bool = False) collections.abc.Callable[[collections.abc.Callable[Ellipsis, Any]], Tool]

Promote a callable to a Tool.

Use as @tool (bare) or @tool(name=..., description=..., destructive=...). The schema is derived from parameter annotations; primitive types map to their JSON-Schema equivalents, anything else falls back to string.

jeevesagent.tools.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).