A lightweight, embeddable Python sandbox for LLM tool execution
Project description
Littrs is a Python sandbox that you embed directly into your Rust or Python application. There's no container to start, no runtime to boot, no network call to make — just a library that executes LLM-generated Python safely, with only the tools you give it.
It was built for a specific workflow: an LLM writes Python code that calls your functions, and you need to run that code without giving it access to anything else. Littrs compiles Python to bytecode and runs it on a stack-based VM with zero ambient capabilities. The only way sandboxed code can interact with the outside world is through tools you explicitly register.
Installation
pip install littrs
Quick Start
from littrs import Sandbox
sandbox = Sandbox()
@sandbox.tool
def get_weather(city: str, units: str = "celsius") -> dict:
"""Get current weather for a city."""
return {"city": city, "temp": 22, "units": units}
result = sandbox("get_weather('London')")
# result == {"city": "London", "temp": 22, "units": "celsius"}
The @sandbox.tool decorator registers your function with its full signature — the LLM code calls it like a normal Python function. The sandbox is also callable: sandbox(code) is shorthand for sandbox.run(code).
Variables persist across calls, and you can inject values directly:
sandbox["user_id"] = 42
sandbox("name = get_weather('London')['city']")
sandbox("name") # "London"
Resource Limits
Prevent runaway code from consuming unbounded resources:
sandbox.limit(max_instructions=10_000, max_recursion_depth=50)
try:
sandbox.run("while True: pass")
except RuntimeError as e:
print(e) # "Instruction limit exceeded (limit: 10000)"
Resource limit errors are uncatchable — try/except in the sandbox code cannot suppress them. This is by design: the host must always be able to regain control.
Capturing Print Output
capture() returns both the result and everything that was print()-ed:
result, printed = sandbox.capture("""
for i in range(5):
print(i)
"done"
""")
# result == "done"
# printed == ["0", "1", "2", "3", "4"]
Tool Documentation for LLM Prompts
describe() auto-generates Python-style signatures and docstrings from registered tools, ready to embed in a system prompt:
print(sandbox.describe())
# def get_weather(city: str, units: str = 'celsius') -> dict:
# """Get current weather for a city."""
Low-level Registration
If you need to bypass the decorator (e.g. registering a function that takes raw positional args):
def fetch_data(args):
return {"id": args[0], "name": "Example"}
sandbox.register("fetch_data", fetch_data)
File Mounting
Mount host files into the sandbox so LLM-generated code can read input and write output without full filesystem access:
sandbox.mount("data.json", "./data/input.json") # read-only (default)
sandbox.mount("output.txt", "./output/result.txt", writable=True) # read-write
result = sandbox("""
f = open("data.json")
data = f.read()
f.close()
f = open("output.txt", "w")
f.write("processed: " + data)
f.close()
""")
# Inspect written files from the host
sandbox.files() # {"output.txt": "processed: ..."}
Unmounted paths raise FileNotFoundError; writing to read-only mounts raises PermissionError. Both are catchable with try/except inside the sandbox.
WASM Sandbox (Stronger Isolation)
For stronger isolation, Littrs can run the interpreter inside a WebAssembly guest module with memory isolation and fuel-based computation limits:
from littrs import WasmSandbox, WasmSandboxConfig
config = WasmSandboxConfig().with_fuel(1_000_000).with_max_memory(32 * 1024 * 1024)
sandbox = WasmSandbox(config)
result = sandbox.run("sum(range(100))")
assert result == 4950
Littrs does not support third-party packages, classes, closures, async/await, finally, or match. See the full list of supported Python features.
Citation
If you use Littrs in your research, please cite it as:
@software{littrs,
title = {Littrs: A Minimal, Secure Python Sandbox for AI Agents},
author = {Chonkie Inc.},
url = {https://github.com/chonkie-inc/littrs},
license = {Apache-2.0},
year = {2025}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file littrs-0.6.2-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: littrs-0.6.2-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 7.9 MB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b6169417f3ab1cdce2cf6ef915ba1eba07fba69e08a81b1b3fda9bdb99dbee5
|
|
| MD5 |
cee7d9d20bb106bdd90ff9be813d82d0
|
|
| BLAKE2b-256 |
24fc6e472993789494e86a517268d94142b4267b6d8fe57da1e63cb88ba83a09
|
File details
Details for the file littrs-0.6.2-cp312-cp312-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: littrs-0.6.2-cp312-cp312-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 9.1 MB
- Tags: CPython 3.12, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9efd1cc74bdffdb156d27005fc5bb8b1a9e6971725b81d672a93347c551de14
|
|
| MD5 |
3385bb21b48f2e92812c201e7b296fbd
|
|
| BLAKE2b-256 |
5dc0e3051fb2930eef391b2c27f298eb612c9c68afd65c9dccf3d2816cc91c57
|
File details
Details for the file littrs-0.6.2-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: littrs-0.6.2-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 7.9 MB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54681e7b5c4345a17933ad4165d9db9d33464f323f8ac1a37ffd2392eda9e809
|
|
| MD5 |
d24a242d70ac5260c91e4fbc1c9254a7
|
|
| BLAKE2b-256 |
dea6f39710ebb9f2f27d43f8b383187b34935027d54d4d7c1babdc54f07b5277
|