Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-python.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PyPI publishing workflow for bashkit Python bindings
# PyPI publishing workflow for bashkit Python package
# Builds pre-compiled wheels for all major platforms and publishes to PyPI.
# Triggered alongside publish.yml on GitHub Release or manual dispatch.
# Adapted from https://github.com/pydantic/monty CI wheel-building pattern.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CI for bashkit Python bindings
# CI for bashkit Python package
# Builds the native extension via maturin and runs pytest on each PR.
# Complements publish-python.yml (release-only) with per-PR validation.

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Fix root cause. Unsure: read more code; if stuck, ask w/ short options. Unrecogn
| 011-python-builtin | Embedded Python via Monty, security, resource limits |
| 012-eval | LLM evaluation harness, dataset format, scoring |
| 012-maintenance | Pre-release maintenance checklist |
| 013-python-package | Python bindings, PyPI wheels, platform matrix |
| 013-python-package | Python package, PyPI wheels, platform matrix |
| 014-scripted-tool-orchestration | Compose ToolDef+callback pairs into OrchestratorTool via bash scripts |

### Documentation
Expand Down Expand Up @@ -88,7 +88,7 @@ just pre-pr # Pre-PR checks

### Python

- Python bindings in `crates/bashkit-python/`
- Python package in `crates/bashkit-python/`
- Linter/formatter: `ruff` (config in `pyproject.toml`)
- `ruff check crates/bashkit-python` and `ruff format --check crates/bashkit-python`
- Tests: `pytest crates/bashkit-python/tests/ -v` (requires `maturin develop` first)
Expand Down
6 changes: 3 additions & 3 deletions crates/bashkit-python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Python bindings for Bashkit
# Bashkit Python package
# Exposes Bashkit as a Python module via PyO3

[package]
Expand All @@ -8,7 +8,7 @@ edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
description = "Python bindings for Bashkit virtual bash interpreter"
description = "A sandboxed bash interpreter for AI agents"

[lib]
crate-type = ["cdylib"]
Expand All @@ -19,7 +19,7 @@ doc = false # Python extension, no Rust docs needed
# Bashkit core
bashkit = { path = "../bashkit", features = ["scripted_tool"] }

# PyO3 for Python bindings
# PyO3 native extension
pyo3 = { workspace = true }
pyo3-async-runtimes = { workspace = true }

Expand Down
125 changes: 76 additions & 49 deletions crates/bashkit-python/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
# Bashkit Python Bindings
# Bashkit

Python bindings for [Bashkit](https://github.com/everruns/bashkit) - a virtual bash interpreter for AI agents.
A sandboxed bash interpreter for AI agents.

```python
from bashkit import BashTool

tool = BashTool()
result = tool.execute_sync("echo 'Hello, World!'")
print(result.stdout) # Hello, World!
```

## Features

- **Sandboxed, in-process execution**: All commands run in isolation with a virtual filesystem
- **68+ built-in commands**: echo, cat, grep, sed, awk, jq, curl, find, and more
- **Full bash syntax**: Variables, pipelines, redirects, loops, functions, arrays
- **Resource limits**: Protect against infinite loops and runaway scripts
- **LangChain integration**: Ready-to-use tool for LangChain agents
- **Sandboxed execution** — all commands run in-process with a virtual filesystem, no containers needed
- **68+ built-in commands** echo, cat, grep, sed, awk, jq, curl, find, and more
- **Full bash syntax** — variables, pipelines, redirects, loops, functions, arrays
- **Resource limits** — protect against infinite loops and runaway scripts
- **Framework integrations** — LangChain, PydanticAI, and Deep Agents

## Installation

```bash
# From PyPI (when published)
pip install bashkit

# With LangChain support
# With framework support
pip install 'bashkit[langchain]'

# From source
pip install maturin
maturin develop
pip install 'bashkit[pydantic-ai]'
```

## Quick Start
## Usage

### Async

```python
import asyncio
Expand All @@ -51,29 +57,17 @@ async def main():
asyncio.run(main())
```

## LangChain Integration
### Sync

```python
from bashkit.langchain import create_bash_tool
from langchain.agents import create_agent

# Create tool
bash_tool = create_bash_tool()

# Create agent
agent = create_agent(
model="claude-sonnet-4-20250514",
tools=[bash_tool],
system_prompt="You are a helpful assistant with bash skills."
)
from bashkit import BashTool

# Run
result = agent.invoke({
"messages": [{"role": "user", "content": "Create a file with today's date"}]
})
tool = BashTool()
result = tool.execute_sync("echo 'Hello!'")
print(result.stdout)
```

## Configuration
### Configuration

```python
tool = BashTool(
Expand All @@ -84,35 +78,68 @@ tool = BashTool(
)
```

## Synchronous API
### Scripted Tool Orchestration

Compose multiple tools into a single bash-scriptable interface:

```python
from bashkit import BashTool
from bashkit import ScriptedTool

tool = BashTool()
result = tool.execute_sync("echo 'Hello!'")
print(result.stdout)
tool = ScriptedTool("api")
tool.add_tool("greet", "Greet a user", callback=lambda p, s=None: f"hello {p.get('name', 'world')}")
result = tool.execute_sync("greet --name Alice")
print(result.stdout) # hello Alice
```

### LangChain

```python
from bashkit.langchain import create_bash_tool

bash_tool = create_bash_tool()
# Use with any LangChain agent
```

### PydanticAI

```python
from bashkit.pydantic_ai import create_bash_tool

bash_tool = create_bash_tool()
# Use with any PydanticAI agent
```

## API Reference

### BashTool

- `execute(commands: str) -> ExecResult`: Execute commands asynchronously
- `execute_sync(commands: str) -> ExecResult`: Execute commands synchronously
- `description() -> str`: Get tool description
- `help() -> str`: Get LLM documentation
- `input_schema() -> str`: Get JSON input schema
- `output_schema() -> str`: Get JSON output schema
- `execute(commands: str) -> ExecResult` — execute commands asynchronously
- `execute_sync(commands: str) -> ExecResult` — execute commands synchronously
- `reset()` — reset interpreter state
- `description() -> str` — tool description for LLM integration
- `help() -> str` — detailed documentation
- `input_schema() -> str` — JSON input schema
- `output_schema() -> str` — JSON output schema

### ExecResult

- `stdout: str`: Standard output
- `stderr: str`: Standard error
- `exit_code: int`: Exit code (0 = success)
- `error: Optional[str]`: Error message if execution failed
- `success: bool`: True if exit_code == 0
- `to_dict() -> dict`: Convert to dictionary
- `stdout: str` — standard output
- `stderr: str` — standard error
- `exit_code: int` — exit code (0 = success)
- `error: Optional[str]` — error message if execution failed
- `success: bool` — True if exit_code == 0
- `to_dict() -> dict` — convert to dictionary

### ScriptedTool

- `add_tool(name, description, callback, schema=None)` — register a tool
- `execute(script: str) -> ExecResult` — execute script asynchronously
- `execute_sync(script: str) -> ExecResult` — execute script synchronously
- `env(key: str, value: str)` — set environment variable

## How it works

Bashkit is built on top of [Bashkit core](https://github.com/everruns/bashkit), a bash interpreter written in Rust. The Python package provides a native extension for fast, sandboxed execution without spawning subprocesses or containers.

## License

Expand Down
6 changes: 2 additions & 4 deletions crates/bashkit-python/bashkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""
Bashkit Python Bindings

A sandboxed bash interpreter for AI agents with virtual filesystem.
Bashkit — a sandboxed bash interpreter for AI agents.

Example:
>>> from bashkit import BashTool
>>> tool = BashTool()
>>> result = await tool.execute("echo 'Hello, World!'")
>>> result = tool.execute_sync("echo 'Hello, World!'")
>>> print(result.stdout)
Hello, World!

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "bashkit"
dynamic = ["version"]
description = "Python bindings for Bashkit - a sandboxed bash interpreter for AI agents"
description = "A sandboxed bash interpreter for AI agents"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.9"
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Python bindings for Bashkit
//! Bashkit Python package
//!
//! Exposes the Bash interpreter and ScriptedTool as Python classes for use in
//! AI agent frameworks. BashTool provides stateful execution (filesystem persists
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit-python/tests/test_bashkit.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for bashkit Python bindings."""
"""Tests for bashkit Python package."""

import json

Expand Down
2 changes: 1 addition & 1 deletion specs/001-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl BashTool {
### Single crate vs workspace
Rejected single crate because:
- CLI binary would bloat the library
- Future Python bindings need separate crate
- Python package needs separate crate
- Cleaner separation of concerns

### Sync vs async filesystem
Expand Down
2 changes: 1 addition & 1 deletion specs/006-threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ the session-level backstop.
| TM-DOS-037 | OverlayFs chmod CoW bypass | `chmod` copy-on-write writes to unlimited upper layer, bypassing overlay limits | — | **OPEN** |
| TM-DOS-038 | OverlayFs incomplete recursive whiteout | `rm -r /dir` only whiteouts directory, not children; lower layer files remain visible | — | **OPEN** |
| TM-DOS-039 | Missing validate_path in VFS methods | `remove`, `stat`, `read_dir`, `copy`, `rename`, `symlink`, `chmod` skip `validate_path()` | — | **OPEN** |
| TM-DOS-040 | Integer truncation on 32-bit | `u64 as usize` casts in network/Python bindings silently truncate on 32-bit, bypassing size checks | — | **OPEN** |
| TM-DOS-040 | Integer truncation on 32-bit | `u64 as usize` casts in network/Python extension silently truncate on 32-bit, bypassing size checks | — | **OPEN** |

**TM-DOS-034**: `InMemoryFs::append_file()` (line 816-896) reads under a read lock, drops it,
checks limits with stale data, then acquires write lock. Fix: single write lock for whole operation.
Expand Down
2 changes: 1 addition & 1 deletion specs/008-release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Example:

- `bashkit` on crates.io (core library)
- `bashkit-cli` on crates.io (CLI tool)
- `bashkit` on PyPI (Python bindings, pre-built wheels)
- `bashkit` on PyPI (Python package, pre-built wheels)

## Publishing Order

Expand Down
10 changes: 5 additions & 5 deletions specs/013-python-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Abstract

Bashkit ships Python bindings as pre-built binary wheels on PyPI. Users install with
Bashkit ships a Python package as pre-built binary wheels on PyPI. Users install with
`pip install bashkit` and get a native extension — no Rust toolchain needed.

## Package Layout
Expand All @@ -11,7 +11,7 @@ Bashkit ships Python bindings as pre-built binary wheels on PyPI. Users install
crates/bashkit-python/
├── Cargo.toml # Rust crate (cdylib via PyO3)
├── pyproject.toml # Python package metadata (maturin build backend)
├── src/lib.rs # PyO3 bindings (BashTool, ExecResult)
├── src/lib.rs # PyO3 native module (BashTool, ExecResult)
├── bashkit/
│ ├── __init__.py # Re-exports from native module
│ ├── _bashkit.pyi # Type stubs (PEP 561)
Expand All @@ -20,13 +20,13 @@ crates/bashkit-python/
│ ├── deepagents.py # Deep Agents integration
│ └── pydantic_ai.py # PydanticAI integration
└── tests/
└── test_bashkit.py # Pytest suite for bindings
└── test_bashkit.py # Pytest suite
```

## Build System

- **Build backend**: [maturin](https://github.com/PyO3/maturin) (1.4–2.0)
- **Rust bindings**: [PyO3](https://pyo3.rs/) 0.24 with `extension-module` feature
- **Rust extension**: [PyO3](https://pyo3.rs/) 0.24 with `extension-module` feature
- **Async bridge**: `pyo3-async-runtimes` (tokio runtime)
- **Module name**: `bashkit._bashkit` (native), re-exported as `bashkit`

Expand Down Expand Up @@ -196,7 +196,7 @@ ruff format . # format
## Design Decisions

- **No PGO**: Profile-guided optimization adds build complexity for minimal gain.
Bashkit is a thin PyO3 wrapper — hot paths are in Rust, not Python dispatch.
Bashkit is a thin PyO3 extension — hot paths are in Rust, not Python dispatch.
Can revisit if profiling shows benefit.
- **No exotic architectures**: armv7, ppc64le, s390x, i686 omitted. Target audience
is AI agent developers on standard server/desktop platforms.
Expand Down
Loading