bashkit

Python API reference

Auto-generated reference for the bashkit PyPI package, covering the public classes and functions exported from bashkit. Reflects the latest published release.

Install with pip install bashkit. See the Embedding guide and LLM tools guide for task-oriented walkthroughs.

Bash

Core bash interpreter with virtual filesystem.

State persists between calls — files created in one execute() are available in subsequent calls.

Example (basic):

>>> bash = Bash()
>>> result = await bash.execute("echo 'Hello!'")
>>> print(result.stdout)
Hello!

Example (Python execution with external function handler):

>>> async def handler(fn_name: str, args: list, kwargs: dict) -> Any:
...     return await tool_executor.call(fn_name, kwargs)
>>> bash = Bash(
...     python=True,
...     external_functions=["api_request"],
...     external_handler=handler,
... )
>>> result = await bash.execute("python3 -c 'print(api_request())'")

Constructor

Bash(username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, python: bool = False, sqlite: bool = False, external_functions: list[str] | None = None, external_handler: ExternalHandler | None = None, files: dict[str, str | Callable[[], str]] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> None

Create a new Bash interpreter.

Parameters:

  • username — Custom username (default "user").
  • hostname — Custom hostname (default "bashkit").
  • cwd — Initial working directory for the shell. Sets the starting directory directly instead of running a leading cd.
  • env — Initial environment variables applied before execution, so scripts see them without an export prelude.
  • max_commands — Limit total commands executed.
  • max_loop_iterations — Limit iterations per loop.
  • max_memory — Memory limit in bytes for the VFS.
  • timeout_seconds — Abort execution after this duration.
  • python — Enable embedded Python (python3 builtin).
  • sqlite — Enable embedded SQLite (sqlite/sqlite3 builtin). Defaults to False. When True, the Turso-backed engine is registered and BASHKIT_ALLOW_INPROCESS_SQLITE=1 is injected automatically. Default SqliteLimits apply: 4 MiB script cap, 256 MiB DB cap, 30 s wall-clock budget, resource-affecting PRAGMAs (cache_size, mmap_size, …) rejected, ATTACH/DETACH rejected.
  • external_functions — Function names callable from Python code.
  • external_handler — Async callback for external function calls. The callback must not call back into the same Bash instance via live methods like read_file(), fs(), or execute(); those re-entrant calls are rejected.
  • files — Dict mapping VFS paths to file contents or lazy callables.
  • mounts — List of real host directory mount configs.
  • allowed_mount_paths — Host path prefixes allowed for real filesystem mounts. Required when mounting sensitive host locations such as paths under a user home directory.
  • readonly_filesystem — Deny all filesystem mutations after configured files and mounts are applied.
  • custom_builtins — Constructor-time Python callbacks exposed as bash builtins. Each callback receives a BuiltinContext with raw argv tokens, optional pipeline stdin, and a live fs handle to the virtual filesystem, and must return a stdout string, a BuiltinResult, or await either. Async callbacks run on the caller’s active asyncio loop for await execute() and on a private loop for execute_sync().
  • network — Optional outbound HTTP / network configuration. Pass {"allow": [...]} for an explicit allowlist or {"allow_all": True} to allow every URL (mirrors NetworkAllowlist::allow_all() in the Rust core). Set "block_private_ips": False to relax the SSRF guard. Add "credentials": [...] to inject headers transparently for matching URLs and "credential_placeholders": [...] to expose opaque placeholder env vars that are replaced with the real secret on the wire. When omitted, network access is disabled (current default). Preserved across reset() and from_snapshot() — placeholder env vars are regenerated on each rebuild.

Example:

>>> bash = Bash(
...     timeout_seconds=30,
...     files={"/input.txt": "some data"},
...     custom_builtins={"ping": lambda ctx: "pong\n"},
...     network={
...         "allow": ["https://api.github.com"],
...         "credentials": [
...             {
...                 "pattern": "https://api.github.com",
...                 "kind": "bearer",
...                 "token": "ghp_xxx",
...             }
...         ],
...     },
... )

execute

Bash.execute(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute bash commands asynchronously.

Parameters:

  • commands — Bash script to run (like bash -c "commands").
  • on_output — Optional callback receiving chunked (stdout, stderr) pairs during execution. Must be synchronous.

Async custom_builtins callbacks run on the caller’s active asyncio loop.

Returns: ExecResult with stdout, stderr, exit_code.

Example:

>>> bash = Bash()
>>> result = await bash.execute("echo hello && echo world")
>>> print(result.stdout)
hello
world

execute_sync

Bash.execute_sync(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute bash commands synchronously (blocking).

Not supported when external_handler is configured — use execute() (async) instead. on_output must be synchronous. Async custom_builtins callbacks run on a private loop here. When called from inside a running event loop (e.g. Jupyter / IPython), callbacks are dispatched to a background thread with their own loop so that asyncio’s “cannot run while another loop is running” restriction is not triggered.

Example:

>>> bash = Bash()
>>> result = bash.execute_sync("date +%Y")
>>> print(result.exit_code)
0

execute_or_throw

Bash.execute_or_throw(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute commands asynchronously; raise BashError on non-zero exit.

on_output must be synchronous.

Example:

>>> bash = Bash()
>>> result = await bash.execute_or_throw("echo ok")
>>> # Raises BashError if the command fails:
>>> await bash.execute_or_throw("false")  # doctest: +SKIP
Traceback (most recent call last):
    ...
BashError: ...

execute_sync_or_throw

Bash.execute_sync_or_throw(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute commands synchronously; raise BashError on non-zero exit.

on_output must be synchronous.

Example:

>>> bash = Bash()
>>> result = bash.execute_sync_or_throw("echo ok")
>>> print(result.stdout.strip())
ok

cancel

Bash.cancel() -> None

Cancel the currently running execution.

Safe to call from any thread. Execution aborts at the next command boundary.

Example:

>>> import threading
>>> bash = Bash()
>>> threading.Timer(1.0, bash.cancel).start()
>>> # Long-running command will be cancelled after 1 second

clear_cancel

Bash.clear_cancel() -> None

Clear the cancellation flag so subsequent executions proceed normally.

Call this after a cancel() once the in-flight execution has finished and you want to reuse the same Bash instance (preserving VFS state). Without this, every future execute() will immediately fail with "execution cancelled".

Note: Calling this while an execution is still in-flight may allow that execution to continue past the cancellation point. Wait for the cancelled execution to finish before clearing (await the async call or let execute_sync return).

Example:

>>> bash = Bash()
>>> bash.cancel()
>>> bash.clear_cancel()
>>> result = bash.execute_sync("echo ok")
>>> result.exit_code
0

reset

Bash.reset() -> None

Reset interpreter to initial state.

Clears all VFS contents, environment variables, and shell state. Re-applies the original files, mounts, and custom_builtins configuration.

Example:

>>> bash = Bash()
>>> bash.execute_sync("echo hi > /tmp/file.txt")
>>> bash.reset()
>>> result = bash.execute_sync("cat /tmp/file.txt")
>>> result.exit_code  # file is gone after reset
1

snapshot

Bash.snapshot(exclude_filesystem: bool = False, exclude_functions: bool = False) -> bytes

Serialize interpreter state to bytes.

snapshot_keyed

Bash.snapshot_keyed(key: bytes, exclude_filesystem: bool = False, exclude_functions: bool = False) -> bytes

Serialize interpreter state to HMAC-protected bytes.

shell_state

Bash.shell_state() -> ShellState

Capture a read-only shell-state snapshot.

restore_snapshot

Bash.restore_snapshot(data: bytes) -> None

Restore interpreter state from bytes produced by snapshot().

restore_snapshot_keyed

Bash.restore_snapshot_keyed(data: bytes, key: bytes) -> None

Restore interpreter state from bytes produced by snapshot_keyed().

from_snapshot

Bash.from_snapshot(data: bytes, username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, python: bool = False, sqlite: bool = False, external_functions: list[str] | None = None, external_handler: ExternalHandler | None = None, files: dict[str, str] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> Bash

Create a new Bash from snapshot bytes and optional constructor kwargs.

from_snapshot_keyed

Bash.from_snapshot_keyed(data: bytes, key: bytes, username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, python: bool = False, sqlite: bool = False, external_functions: list[str] | None = None, external_handler: ExternalHandler | None = None, files: dict[str, str] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> Bash

Create a new Bash from HMAC-protected snapshot bytes.

read_file

Bash.read_file(path: str) -> str

Read a VFS file as UTF-8 text.

write_file

Bash.write_file(path: str, content: str) -> None

Write UTF-8 text into the VFS.

append_file

Bash.append_file(path: str, content: str) -> None

Append UTF-8 text to a VFS file.

mkdir

Bash.mkdir(path: str, recursive: bool = False) -> None

Create a directory in the VFS.

exists

Bash.exists(path: str) -> bool

Return whether a VFS path exists.

remove

Bash.remove(path: str, recursive: bool = False) -> None

Remove a VFS file or directory.

stat

Bash.stat(path: str) -> dict[str, Any]

Return metadata for a VFS path.

chmod

Bash.chmod(path: str, mode: int) -> None

Change VFS permissions for a path.

Bash.symlink(target: str, link: str) -> None

Create a symlink in the VFS.

Bash.read_link(path: str) -> str

Return the symlink target for a VFS path.

read_dir

Bash.read_dir(path: str) -> list[dict[str, Any]]

Return directory entries with metadata.

ls

Bash.ls(path: str = '.') -> list[str]

Return entry names for a directory, or an empty list if it is missing.

glob

Bash.glob(pattern: str) -> list[str]

Return file paths matching a safe glob pattern.

fs

Bash.fs() -> FileSystem

Return a live filesystem handle.

Each operation acquires the interpreter lock, so the handle always reflects the latest state (including after reset()).

Example:

>>> bash = Bash()
>>> bash.execute_sync("echo hello > /greeting.txt")
>>> fs = bash.fs()
>>> fs.read_file("/greeting.txt")
b'hello\n'

mount

Bash.mount(vfs_path: str, fs: FileSystem) -> None

Mount an external filesystem at the given VFS path.

Parameters:

  • vfs_path — Mount point inside the VFS.
  • fs — FileSystem instance to mount.

Example:

>>> bash = Bash()
>>> overlay = FileSystem()
>>> overlay.write_file("/data.csv", b"a,b,c")
>>> bash.mount("/mnt/data", overlay)
>>> result = bash.execute_sync("cat /mnt/data/data.csv")
>>> print(result.stdout)
a,b,c

unmount

Bash.unmount(vfs_path: str) -> None

Unmount a previously mounted filesystem.

Example:

>>> bash = Bash()
>>> overlay = FileSystem()
>>> bash.mount("/mnt/ext", overlay)
>>> bash.unmount("/mnt/ext")

BashTool

Sandboxed bash interpreter for AI agents.

BashTool provides a safe execution environment for running bash commands with a virtual filesystem. All file operations are contained within the sandbox - no access to the real filesystem.

Adds LLM-facing contract metadata (description, system_prompt, input_schema, output_schema) on top of the core interpreter.

Fields

  • namestr
  • short_descriptionstr
  • versionstr

Constructor

BashTool(username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, files: dict[str, str | Callable[[], str]] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> None

Create a new BashTool.

Parameters:

  • username — Custom username (default "user").
  • hostname — Custom hostname (default "bashkit").
  • cwd — Initial working directory for the shell. Sets the starting directory directly instead of running a leading cd.
  • env — Initial environment variables applied before execution, so scripts see them without an export prelude.
  • max_commands — Limit total commands executed.
  • max_loop_iterations — Limit iterations per loop.
  • max_memory — Memory limit in bytes for the VFS.
  • timeout_seconds — Abort execution after this duration.
  • files — Dict mapping VFS paths to file contents or lazy callables.
  • mounts — List of real host directory mount configs.
  • allowed_mount_paths — Host path prefixes allowed for real filesystem mounts. Required when mounting sensitive host locations such as paths under a user home directory.
  • readonly_filesystem — Deny all filesystem mutations after configured files and mounts are applied.
  • custom_builtins — Constructor-time Python callbacks exposed as bash builtins. Each callback receives a BuiltinContext (including a live fs handle to the virtual filesystem) and must return a stdout string, a BuiltinResult, or await either. Async callbacks run on the caller’s active asyncio loop for await execute() and on a private loop for execute_sync().
  • network — Optional outbound HTTP / network configuration. See Bash.__init__ for accepted keys. Preserved across reset() and from_snapshot().

Example:

>>> tool = BashTool(
...     timeout_seconds=30,
...     custom_builtins={"ping": lambda ctx: "pong\n"},
...     network={"allow_all": True},
... )
>>> print(tool.name)
bash

execute

BashTool.execute(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute bash commands asynchronously.

Async custom_builtins callbacks run on the caller’s active asyncio loop.

on_output must be synchronous.

Example:

>>> tool = BashTool()
>>> result = await tool.execute("ls /")
>>> result.success
True

execute_sync

BashTool.execute_sync(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute bash commands synchronously (blocking).

Async custom_builtins callbacks run on a private loop here. When called from inside a running event loop (e.g. Jupyter / IPython), callbacks are dispatched to a background thread with their own loop so that asyncio’s “cannot run while another loop is running” restriction is not triggered.

on_output must be synchronous.

Example:

>>> tool = BashTool()
>>> result = tool.execute_sync("echo 42")
>>> result.stdout.strip()
'42'

execute_or_throw

BashTool.execute_or_throw(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute commands asynchronously; raise BashError on non-zero exit.

on_output must be synchronous.

Example:

>>> tool = BashTool()
>>> result = await tool.execute_or_throw("echo ok")
>>> result.success
True

execute_sync_or_throw

BashTool.execute_sync_or_throw(commands: str, on_output: OutputHandler | None = None) -> ExecResult

Execute commands synchronously; raise BashError on non-zero exit.

on_output must be synchronous.

Example:

>>> tool = BashTool()
>>> result = tool.execute_sync_or_throw("echo ok")
>>> result.stdout.strip()
'ok'

cancel

BashTool.cancel() -> None

Cancel the currently running execution.

Safe to call from any thread.

Example:

>>> tool = BashTool()
>>> tool.cancel()  # no-op if nothing is running

clear_cancel

BashTool.clear_cancel() -> None

Clear the cancellation flag so subsequent executions proceed normally.

Call this after a cancel() once the in-flight execution has finished and you want to reuse the same BashTool instance (preserving VFS state). Without this, every future execute() will immediately fail with "execution cancelled".

Note: Calling this while an execution is still in-flight may allow that execution to continue past the cancellation point. Wait for the cancelled execution to finish before clearing (await the async call or let execute_sync return).

Example:

>>> tool = BashTool()
>>> tool.cancel()
>>> tool.clear_cancel()
>>> result = tool.execute_sync("echo ok")
>>> result.exit_code
0

description

BashTool.description() -> str

Return the tool description for LLM consumption.

Example:

>>> tool = BashTool()
>>> desc = tool.description()
>>> "bash" in desc.lower()
True

help

BashTool.help() -> str

Return extended help text.

Example:

>>> tool = BashTool()
>>> help_text = tool.help()
>>> len(help_text) > 0
True

system_prompt

BashTool.system_prompt() -> str

Return the system prompt for LLM agents.

Includes tool description, usage guidelines, and capabilities.

Example:

>>> tool = BashTool()
>>> prompt = tool.system_prompt()
>>> "sandbox" in prompt.lower() or "bash" in prompt.lower()
True

input_schema

BashTool.input_schema() -> str

Return the JSON Schema for tool input.

Example:

>>> import json
>>> tool = BashTool()
>>> schema = json.loads(tool.input_schema())
>>> "commands" in str(schema)
True

output_schema

BashTool.output_schema() -> str

Return the JSON Schema for tool output.

Example:

>>> import json
>>> tool = BashTool()
>>> schema = json.loads(tool.output_schema())
>>> isinstance(schema, dict)
True

reset

BashTool.reset() -> None

Reset the tool to initial state.

Clears VFS, environment, and shell state while re-applying constructor-time custom_builtins.

Example:

>>> tool = BashTool()
>>> tool.execute_sync("touch /tmp/file")
>>> tool.reset()
>>> result = tool.execute_sync("test -f /tmp/file")
>>> result.exit_code  # file is gone
1

snapshot

BashTool.snapshot(exclude_filesystem: bool = False, exclude_functions: bool = False) -> bytes

Serialize interpreter state to bytes.

snapshot_keyed

BashTool.snapshot_keyed(key: bytes, exclude_filesystem: bool = False, exclude_functions: bool = False) -> bytes

Serialize interpreter state to HMAC-protected bytes.

shell_state

BashTool.shell_state() -> ShellState

Capture a read-only shell-state snapshot.

restore_snapshot

BashTool.restore_snapshot(data: bytes) -> None

Restore interpreter state from bytes produced by snapshot().

restore_snapshot_keyed

BashTool.restore_snapshot_keyed(data: bytes, key: bytes) -> None

Restore interpreter state from bytes produced by snapshot_keyed().

from_snapshot

BashTool.from_snapshot(data: bytes, username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, files: dict[str, str] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> BashTool

Create a new BashTool from snapshot bytes and optional constructor kwargs.

from_snapshot_keyed

BashTool.from_snapshot_keyed(data: bytes, key: bytes, username: str | None = None, hostname: str | None = None, cwd: str | None = None, env: Mapping[str, str] | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, max_memory: int | None = None, timeout_seconds: float | None = None, files: dict[str, str] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, custom_builtins: Mapping[str, BuiltinCallback] | None = None, network: NetworkConfig | None = None) -> BashTool

Create a new BashTool from HMAC-protected snapshot bytes.

read_file

BashTool.read_file(path: str) -> str

Read a VFS file as UTF-8 text.

write_file

BashTool.write_file(path: str, content: str) -> None

Write UTF-8 text into the VFS.

append_file

BashTool.append_file(path: str, content: str) -> None

Append UTF-8 text to a VFS file.

mkdir

BashTool.mkdir(path: str, recursive: bool = False) -> None

Create a directory in the VFS.

exists

BashTool.exists(path: str) -> bool

Return whether a VFS path exists.

remove

BashTool.remove(path: str, recursive: bool = False) -> None

Remove a VFS file or directory.

stat

BashTool.stat(path: str) -> dict[str, Any]

Return metadata for a VFS path.

chmod

BashTool.chmod(path: str, mode: int) -> None

Change VFS permissions for a path.

BashTool.symlink(target: str, link: str) -> None

Create a symlink in the VFS.

BashTool.read_link(path: str) -> str

Return the symlink target for a VFS path.

read_dir

BashTool.read_dir(path: str) -> list[dict[str, Any]]

Return directory entries with metadata.

ls

BashTool.ls(path: str = '.') -> list[str]

Return entry names for a directory, or an empty list if it is missing.

glob

BashTool.glob(pattern: str) -> list[str]

Return file paths matching a safe glob pattern.

fs

BashTool.fs() -> FileSystem

Return a live filesystem handle.

Each operation acquires the interpreter lock, so the handle always reflects the latest state (including after reset()).

Example:

>>> tool = BashTool()
>>> tool.execute_sync("echo data > /out.txt")
>>> fs = tool.fs()
>>> fs.read_file("/out.txt")
b'data\n'

mount

BashTool.mount(vfs_path: str, fs: FileSystem) -> None

Mount an external filesystem at the given VFS path.

Example:

>>> tool = BashTool()
>>> ext = FileSystem()
>>> ext.write_file("/info.txt", b"external")
>>> tool.mount("/mnt/ext", ext)
>>> result = tool.execute_sync("cat /mnt/ext/info.txt")
>>> result.stdout.strip()
'external'

unmount

BashTool.unmount(vfs_path: str) -> None

Unmount a previously mounted filesystem.

Example:

>>> tool = BashTool()
>>> ext = FileSystem()
>>> tool.mount("/mnt/ext", ext)
>>> tool.unmount("/mnt/ext")

ScriptedTool

Compose Python callbacks as bash builtins for multi-tool orchestration.

Each registered tool becomes a bash builtin command. An LLM (or user) writes a single bash script that pipes, loops, and branches across tools.

Fields

  • namestr
  • short_descriptionstr
  • versionstr

Constructor

ScriptedTool(name: str, short_description: str | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None) -> None

Create a new ScriptedTool.

Parameters:

  • name — Tool name (used as the LLM tool identifier).
  • short_description — One-line description of the tool.
  • max_commands — Limit total commands per execution.
  • max_loop_iterations — Limit iterations per loop.

Example:

>>> tool = ScriptedTool("data_pipeline", short_description="ETL tools")
>>> print(tool.name)
data_pipeline

add_tool

ScriptedTool.add_tool(name: str, description: str, callback: Callable[[dict[str, Any], str | None], str], schema: dict[str, Any] | None = None) -> None

Register a Python callback as a bash builtin command.

Parameters:

  • name — Command name (becomes a bash builtin).
  • description — Human-readable description of the sub-tool.
  • callback(params_dict, stdin_or_none) -> output_string or an async callback that resolves to one. Async callbacks run on the caller’s active asyncio loop for await execute() and on a private loop for execute_sync().
  • schema — Optional JSON Schema for the tool’s parameters.

Example:

>>> tool = ScriptedTool("math")
>>> tool.add_tool(
...     "add", "Add two numbers",
...     callback=lambda p, s=None: str(int(p["a"]) + int(p["b"])) + "\n",
...     schema={
...         "type": "object",
...         "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}},
...     },
... )
>>> result = tool.execute_sync("add --a 2 --b 3")
>>> result.stdout.strip()
'5'

env

ScriptedTool.env(key: str, value: str) -> None

Set an environment variable for subsequent executions.

Example:

>>> tool = ScriptedTool("demo")
>>> tool.env("API_KEY", "secret-123")
>>> result = tool.execute_sync("echo $API_KEY")
>>> result.stdout.strip()
'secret-123'

execute

ScriptedTool.execute(commands: str) -> ExecResult

Execute commands asynchronously.

Async callbacks run on the caller’s active asyncio loop.

Example:

>>> tool = ScriptedTool("demo")
>>> tool.add_tool("hi", "Say hi", callback=lambda p, s=None: "hi\n")
>>> result = await tool.execute("hi")
>>> result.stdout.strip()
'hi'

execute_sync

ScriptedTool.execute_sync(commands: str) -> ExecResult

Execute commands synchronously (blocking).

Async callbacks run on a private loop here.

Example:

>>> tool = ScriptedTool("demo")
>>> tool.add_tool("ping", "Ping", callback=lambda p, s=None: "pong\n")
>>> result = tool.execute_sync("ping")
>>> result.stdout.strip()
'pong'

tool_count

ScriptedTool.tool_count() -> int

Return the number of registered sub-tools.

Example:

>>> tool = ScriptedTool("demo")
>>> tool.tool_count()
0
>>> tool.add_tool("a", "A", callback=lambda p, s=None: "")
>>> tool.tool_count()
1

description

ScriptedTool.description() -> str

Return the tool description for LLM consumption.

Example:

>>> tool = ScriptedTool("api", short_description="API tools")
>>> desc = tool.description()
>>> len(desc) > 0
True

help

ScriptedTool.help() -> str

Return extended help text listing all registered sub-tools.

Example:

>>> tool = ScriptedTool("api")
>>> tool.add_tool("fetch", "Fetch URL", callback=lambda p, s=None: "")
>>> "fetch" in tool.help()
True

system_prompt

ScriptedTool.system_prompt() -> str

Return the system prompt for LLM agents.

Includes descriptions of all registered sub-tools and usage examples.

Example:

>>> tool = ScriptedTool("api")
>>> tool.add_tool("fetch", "Fetch URL", callback=lambda p, s=None: "")
>>> prompt = tool.system_prompt()
>>> "fetch" in prompt.lower()
True

input_schema

ScriptedTool.input_schema() -> str

Return the JSON Schema for tool input.

Example:

>>> import json
>>> tool = ScriptedTool("api")
>>> schema = json.loads(tool.input_schema())
>>> "commands" in str(schema)
True

output_schema

ScriptedTool.output_schema() -> str

Return the JSON Schema for tool output.

Example:

>>> import json
>>> tool = ScriptedTool("api")
>>> schema = json.loads(tool.output_schema())
>>> isinstance(schema, dict)
True

FileSystem

Direct access to Bashkit’s virtual filesystem or a standalone mountable FS.

Two ways to create:

  1. In-memory (default) — starts empty:
>>> fs = FileSystem()
>>> fs.write_file("/hello.txt", b"hi")
>>> fs.read_file("/hello.txt")
b'hi'
  1. Backed by a real host directory:
>>> fs = FileSystem.real("/tmp/data", writable=False)
>>> fs.exists("/some-host-file.txt")
True

Constructor

FileSystem() -> None

Create a new empty in-memory filesystem.

Example:

>>> fs = FileSystem()
>>> fs.exists("/anything")
False

real

FileSystem.real(host_path: str, writable: bool = False) -> FileSystem

Create a filesystem backed by a real host directory.

Parameters:

  • host_path — Absolute path on the host to expose.
  • writable — Allow write operations (default read-only).

Example:

>>> fs = FileSystem.real("/tmp/project", writable=True)
>>> fs.write_file("/tmp/project/out.txt", b"data")

from_capsule

FileSystem.from_capsule(capsule: Any) -> FileSystem

Create a filesystem from a PyCapsule exported by a native extension.

The capsule must wrap a bashkit.FileSystem.v1 stable ABI handle.

to_capsule

FileSystem.to_capsule() -> Any

Export this filesystem as a stable-ABI PyCapsule for native extension interop.

read_file

FileSystem.read_file(path: str) -> bytes

Read the entire contents of a file.

Parameters:

  • path — Absolute path in the filesystem.

Returns: File contents as bytes.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/demo.txt", b"hello")
>>> fs.read_file("/demo.txt")
b'hello'

write_file

FileSystem.write_file(path: str, content: bytes) -> None

Write content to a file, creating or overwriting it.

Parameters:

  • path — Absolute path in the filesystem.
  • content — Data to write.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/output.txt", b"result data")

append_file

FileSystem.append_file(path: str, content: bytes) -> None

Append content to an existing file.

Parameters:

  • path — Absolute path in the filesystem.
  • content — Data to append.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/log.txt", b"line1\n")
>>> fs.append_file("/log.txt", b"line2\n")
>>> fs.read_file("/log.txt")
b'line1\nline2\n'

mkdir

FileSystem.mkdir(path: str, recursive: bool = False) -> None

Create a directory.

Parameters:

  • path — Absolute path for the new directory.
  • recursive — Create parent directories as needed.

Example:

>>> fs = FileSystem()
>>> fs.mkdir("/a/b/c", recursive=True)
>>> fs.exists("/a/b/c")
True

remove

FileSystem.remove(path: str, recursive: bool = False) -> None

Remove a file or directory.

Parameters:

  • path — Absolute path to remove.
  • recursive — Remove directory contents recursively.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/tmp.txt", b"x")
>>> fs.remove("/tmp.txt")
>>> fs.exists("/tmp.txt")
False

stat

FileSystem.stat(path: str) -> dict[str, Any]

Get file metadata.

Returns: Dict with file_type, size, mode, modified, created.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/f.txt", b"data")
>>> info = fs.stat("/f.txt")
>>> info["file_type"]
'file'
>>> info["size"]
4

read_dir

FileSystem.read_dir(path: str) -> list[dict[str, Any]]

List directory entries.

Returns: List of dicts, each with name and metadata keys.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/dir/a.txt", b"a")
>>> entries = fs.read_dir("/dir")
>>> entries[0]["name"]
'a.txt'

exists

FileSystem.exists(path: str) -> bool

Check whether a path exists.

Example:

>>> fs = FileSystem()
>>> fs.exists("/nope")
False
>>> fs.write_file("/yes.txt", b"")
>>> fs.exists("/yes.txt")
True

rename

FileSystem.rename(from_path: str, to_path: str) -> None

Rename (move) a file or directory.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/old.txt", b"data")
>>> fs.rename("/old.txt", "/new.txt")
>>> fs.exists("/new.txt")
True

copy

FileSystem.copy(from_path: str, to_path: str) -> None

Copy a file.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/src.txt", b"data")
>>> fs.copy("/src.txt", "/dst.txt")
>>> fs.read_file("/dst.txt")
b'data'
FileSystem.symlink(target: str, link: str) -> None

Create a symbolic link.

Parameters:

  • target — Path the symlink points to.
  • link — Path of the symlink itself.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/real.txt", b"data")
>>> fs.symlink("/real.txt", "/link.txt")
>>> fs.read_file("/link.txt")
b'data'

chmod

FileSystem.chmod(path: str, mode: int) -> None

Change file permissions.

Parameters:

  • path — Absolute path.
  • mode — Octal permission bits (e.g. 0o755).

Example:

>>> fs = FileSystem()
>>> fs.write_file("/script.sh", b"#!/bin/bash")
>>> fs.chmod("/script.sh", 0o755)
FileSystem.read_link(path: str) -> str

Read the target of a symbolic link.

Example:

>>> fs = FileSystem()
>>> fs.write_file("/target.txt", b"data")
>>> fs.symlink("/target.txt", "/link.txt")
>>> fs.read_link("/link.txt")
'/target.txt'

ExecResult

Result from executing bash commands.

Example:

>>> bash = Bash()
>>> result = bash.execute_sync("echo hello")
>>> result.success
True
>>> result.stdout
'hello\n'
>>> result.exit_code
0

Fields

  • stdoutstr
  • stderrstr
  • exit_codeint
  • errorstr | None
  • successbool

to_dict

ExecResult.to_dict() -> dict[str, Any]

Convert result to a plain dictionary.

Returns: Dict with stdout, stderr, exit_code, error, stdout_truncated, stderr_truncated, final_env.

Example:

>>> bash = Bash()
>>> result = bash.execute_sync("echo hi")
>>> d = result.to_dict()
>>> d["stdout"]
'hi\n'
>>> d["exit_code"]
0

ShellState

Read-only snapshot of shell state.

Returned by Bash.shell_state() and BashTool.shell_state() for prompt rendering and state inspection. This is a Python-friendly inspection view, not a full-fidelity Rust ShellState mirror. Mapping fields are immutable views. Use snapshot(exclude_filesystem=True) when you need shell-only restore bytes. Transient fields like last_exit_code and traps reflect the captured snapshot, but the next top-level execute() / execute_sync() clears them before running a new command.

Fields

  • envMapping[str, str]
  • variablesMapping[str, str]
  • arraysMapping[str, Mapping[int, str]]
  • assoc_arraysMapping[str, Mapping[str, str]]
  • cwdstr
  • last_exit_codeint
  • aliasesMapping[str, str]
  • trapsMapping[str, str]

BuiltinContext

Invocation context for a custom builtin callback.

Fields

  • namestr
  • argvlist[str]
  • stdinstr | None
  • envdict[str, str]
  • cwdstr
  • fsFileSystem

BuiltinResult

Shell-facing result for a custom builtin callback.

Fields

  • stdoutstr
  • stderrstr
  • exit_codeint

Constructor

BuiltinResult(stdout: str = '', stderr: str = '', exit_code: int = 0) -> None

BashError

Exception raised when a bash command exits with non-zero status.

Example:

>>> bash = Bash()
>>> try:
...     bash.execute_sync_or_throw("exit 42")
... except BashError as e:
...     print(e.exit_code)
42

Fields

  • exit_codeint
  • stderrstr
  • stdoutstr

create_langchain_tool_spec()

create_langchain_tool_spec() -> dict[str, Any]

Create a LangChain-compatible tool specification.

Returns: Dict with name, description, and args_schema.

Example:

>>> spec = create_langchain_tool_spec()
>>> spec["name"]
'bash'

get_version()

get_version() -> str

Get the bashkit version string.

Example:

>>> version = get_version()
>>> isinstance(version, str)
True

Framework integrations

bashkit.langchain

LangChain integration for Bashkit.

Field

bashkit.langchain.Field(*args, **kwargs)

PrivateAttr

bashkit.langchain.PrivateAttr(*args, **kwargs)

create_bash_tool

bashkit.langchain.create_bash_tool(username: str | None = None, hostname: str | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, timeout_seconds: float | None = None, files: dict[str, str | Callable[[], str]] | None = None, mounts: list[dict[str, Any]] | None = None, allowed_mount_paths: list[str] | None = None, readonly_filesystem: bool = False, max_output_length: int = 100000) -> BashkitTool

Create a LangChain-compatible Bashkit tool.

Parameters:

  • username — Custom username for sandbox
  • hostname — Custom hostname for sandbox
  • max_commands — Max commands to execute
  • max_loop_iterations — Max loop iterations
  • timeout_seconds — Execution timeout in seconds. When set, commands that exceed this duration are aborted with exit code 124.
  • files — Static VFS file mounts keyed by sandbox path.
  • mounts — Real host directory mounts exposed inside the sandbox. Mounts are read-only by default; pass {"writable": True} on a mount config to allow writes.
  • allowed_mount_paths — Host path prefixes allowed for real filesystem mounts. Required when mounting sensitive host locations such as paths under a user home directory.
  • readonly_filesystem — Deny all filesystem mutations after configured files and mounts are applied.
  • max_output_length — Maximum number of characters returned to the LangChain agent from one bash tool call before truncation.

Returns: BashkitTool instance for use with LangChain agents

Raises:

  • ImportError — If langchain-core is not installed

create_scripted_tool

bashkit.langchain.create_scripted_tool(scripted_tool: NativeScriptedTool) -> ScriptedToolLangChain

Create a LangChain-compatible tool from a configured ScriptedTool.

Parameters:

  • scripted_tool — A ScriptedTool with registered tool callbacks

Returns: ScriptedToolLangChain instance for use with LangChain agents

Raises:

  • ImportError — If langchain-core is not installed

bashkit.pydantic_ai

PydanticAI integration for Bashkit.

create_bash_tool

bashkit.pydantic_ai.create_bash_tool(username: str | None = None, hostname: str | None = None, max_commands: int | None = None, max_loop_iterations: int | None = None, timeout_seconds: float | None = None, max_output_length: int = 100000) -> Tool

Create a PydanticAI Tool wrapping Bashkit.

Parameters:

  • username — Custom username for sandbox
  • hostname — Custom hostname for sandbox
  • max_commands — Max commands to execute
  • max_loop_iterations — Max loop iterations
  • timeout_seconds — Execution timeout in seconds. When set, commands that exceed this duration are aborted with exit code 124.

Returns: Tool for use with Agent(tools=[...])

Raises:

  • ImportError — If pydantic-ai is not installed

bashkit.deepagents

Deep Agents integration for Bashkit.

create_bash_middleware

bashkit.deepagents.create_bash_middleware(**kwargs) -> BashkitMiddleware

Create BashkitMiddleware for Deep Agents.

create_bashkit_backend

bashkit.deepagents.create_bashkit_backend(**kwargs) -> BashkitBackend

Create BashkitBackend for Deep Agents.