{"id":46793858,"url":"https://github.com/chonkie-inc/littrs","last_synced_at":"2026-03-10T03:09:45.008Z","repository":{"id":337303617,"uuid":"1139543305","full_name":"chonkie-inc/littrs","owner":"chonkie-inc","description":"🏖️🛡️ Keep your LLM's 💩 code where it belongs — in a sandbox.","archived":false,"fork":false,"pushed_at":"2026-03-01T20:11:53.000Z","size":3829,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-01T22:29:22.582Z","etag":null,"topics":["ai-agents","bytecode","llm","pyo3","python","rust","sandbox","security","tool-use","wasm"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chonkie-inc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-22T05:07:44.000Z","updated_at":"2026-03-01T20:11:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chonkie-inc/littrs","commit_stats":null,"previous_names":["chonkie-inc/littrs"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/chonkie-inc/littrs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chonkie-inc%2Flittrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chonkie-inc%2Flittrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chonkie-inc%2Flittrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chonkie-inc%2Flittrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chonkie-inc","download_url":"https://codeload.github.com/chonkie-inc/littrs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chonkie-inc%2Flittrs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30322680,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T01:36:58.598Z","status":"online","status_checked_at":"2026-03-10T02:00:06.579Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","bytecode","llm","pyo3","python","rust","sandbox","security","tool-use","wasm"],"created_at":"2026-03-10T03:09:44.341Z","updated_at":"2026-03-10T03:09:44.994Z","avatar_url":"https://github.com/chonkie-inc.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n![Littrs Logo](https://github.com/chonkie-inc/littrs/blob/main/assets/littrs.png?raw=true)\n\n# Littrs\n\n### Keep your LLM's 💩 code where it belongs — in a sandbox.\n\n[![Crates.io](https://img.shields.io/crates/v/littrs.svg)](https://crates.io/crates/littrs)\n[![PyPI version](https://img.shields.io/pypi/v/littrs.svg)](https://pypi.org/project/littrs/)\n[![License](https://img.shields.io/github/license/chonkie-inc/littrs.svg)](https://github.com/chonkie-inc/littrs/blob/main/LICENSE)\n[![CI](https://github.com/chonkie-inc/littrs/actions/workflows/ci.yml/badge.svg)](https://github.com/chonkie-inc/littrs/actions/workflows/ci.yml)\n[![GitHub stars](https://img.shields.io/github/stars/chonkie-inc/littrs.svg)](https://github.com/chonkie-inc/littrs/stargazers)\n\n\u003c/div\u003e\n\n---\n\nLLMs are better at writing Python than crafting JSON tool calls. But running LLM-generated code means either spinning up containers, paying for sandboxing services, or gambling with `exec()`. Littrs takes a different approach: a Python sandbox that embeds directly into your Rust or Python application as a library. No containers, no network calls, no infrastructure — just `pip install` or `cargo add` and go.\n\nYou register functions as tools, hand the sandbox some LLM-generated code, and get back a result. The sandbox compiles Python to bytecode and runs it on a stack-based VM with zero ambient capabilities — no filesystem, no network, no env vars. The only way sandboxed code can reach the outside world is through tools you explicitly provide.\n\n* **Tool registration** — `@sandbox.tool` in Python, `#[tool]` in Rust. Inject variables with `sandbox[\"x\"] = val`, run code with `sandbox(code)`\n* **Resource limits** — cap bytecode instructions and recursion depth per call, enforced at the VM level and uncatchable by `try`/`except`\n* **Stdout capture** — `print()` output collected and returned separately from the result\n* **Auto-generated tool docs** — `describe()` produces Python-style signatures and docstrings, ready to paste into a system prompt\n* **Built-in modules** — `json`, `math`, and `typing` available out of the box with `Sandbox(builtins=True)` / `Sandbox::with_builtins()`. Register custom modules with `.module()`\n* **File mounting** — mount host files into the sandbox with read-only or read-write access. Sandbox code uses `open()` to read/write; writes persist back to the host. `sandbox.files()` lets you inspect current writable file contents\n* **WASM isolation** — optional stronger sandboxing via an embedded wasmtime guest module with memory and fuel limits\n* **Fast startup** — no interpreter boot, no runtime to load. Create a sandbox, register tools, run code\n\nLittrs implements enough Python for an LLM to call tools, process results, handle errors, and return values. It does not support third-party packages, classes, closures, `async`/`await`, `finally`, or `match` — see the [ROADMAP](ROADMAP.md) for what's planned and the full list of [supported features](FEATURES.md).\n\n## Installation\n\n### Rust\n\n```bash\ncargo add littrs\n```\n\n### Python\n\n```bash\nuv pip install littrs\n```\n\n## Usage\n\nLittrs can be called from Rust or Python. See the [ROADMAP](ROADMAP.md) for planned features.\n\n### Python\n\n```python\nfrom littrs import Sandbox\n\nsandbox = Sandbox()\n\n@sandbox.tool\ndef get_weather(city: str, units: str = \"celsius\") -\u003e dict:\n    \"\"\"Get current weather for a city.\"\"\"\n    return {\"city\": city, \"temp\": 22, \"units\": units}\n\nresult = sandbox(\"get_weather('London')\")\n# result == {\"city\": \"London\", \"temp\": 22, \"units\": \"celsius\"}\n```\n\nThe `@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)`.\n\nVariables persist across calls, and you can inject values directly:\n\n```python\nsandbox[\"user_id\"] = 42\nsandbox(\"name = get_weather('London')['city']\")\nsandbox(\"name\")  # \"London\"\n```\n\n#### Resource Limits\n\n```python\nsandbox.limit(max_instructions=10_000, max_recursion_depth=50)\n\ntry:\n    sandbox.run(\"while True: pass\")\nexcept RuntimeError as e:\n    print(e)  # \"Instruction limit exceeded (limit: 10000)\"\n```\n\nResource limit errors are **uncatchable** — `try`/`except` in the sandbox code cannot suppress them.\n\n#### Capturing Print Output\n\n`capture()` returns both the result and everything that was `print()`-ed:\n\n```python\nresult, printed = sandbox.capture(\"\"\"\nfor i in range(5):\n    print(i)\n\"done\"\n\"\"\")\n# result  == \"done\"\n# printed == [\"0\", \"1\", \"2\", \"3\", \"4\"]\n```\n\n#### Tool Documentation for LLM Prompts\n\n`describe()` auto-generates Python-style signatures and docstrings from registered tools, ready to embed in a system prompt:\n\n```python\nprint(sandbox.describe())\n# def get_weather(city: str, units: str = 'celsius') -\u003e dict:\n#     \"\"\"Get current weather for a city.\"\"\"\n```\n\n#### Low-level Registration\n\nIf you need to bypass the decorator (e.g. registering a function that takes raw positional args):\n\n```python\ndef fetch_data(args):\n    return {\"id\": args[0], \"name\": \"Example\"}\n\nsandbox.register(\"fetch_data\", fetch_data)\n```\n\n#### Imports \u0026 Built-in Modules\n\nCreate a sandbox with `builtins=True` to enable `json`, `math`, and `typing` modules:\n\n```python\nsandbox = Sandbox(builtins=True)\n\nresult = sandbox(\"\"\"\nimport json\ndata = json.loads('{\"name\": \"Alice\", \"score\": 95}')\ndata[\"score\"]\n\"\"\")\n# result == 95\n```\n\n`from ... import` works too:\n\n```python\nsandbox(\"\"\"\nfrom math import sqrt, pi\nsqrt(pi)\n\"\"\")\n```\n\nRegister custom modules with `.module()`:\n\n```python\nsandbox.module(\"config\", {\"version\": \"1.0\", \"debug\": False})\nsandbox(\"import config; config.version\")  # \"1.0\"\n```\n\n#### File Mounting\n\nMount host files into the sandbox so LLM-generated code can read input and write output without full filesystem access:\n\n```python\nsandbox.mount(\"data.json\", \"./data/input.json\")                    # read-only (default)\nsandbox.mount(\"output.txt\", \"./output/result.txt\", writable=True)  # read-write\n\nresult = sandbox(\"\"\"\nf = open(\"data.json\")\ndata = f.read()\nf.close()\n\nf = open(\"output.txt\", \"w\")\nf.write(\"processed: \" + data)\nf.close()\n\"\"\")\n\n# Inspect written files from the host\nsandbox.files()  # {\"output.txt\": \"processed: ...\"}\n```\n\nUnmounted paths raise `FileNotFoundError`; writing to read-only mounts raises `PermissionError`. Both are catchable with `try`/`except` inside the sandbox.\n\n#### WASM Sandbox (Stronger Isolation)\n\nFor stronger isolation, Littrs can run the interpreter inside a WebAssembly guest module with memory isolation and fuel-based computation limits:\n\n```python\nfrom littrs import WasmSandbox, WasmSandboxConfig\n\nconfig = WasmSandboxConfig().with_fuel(1_000_000).with_max_memory(32 * 1024 * 1024)\nsandbox = WasmSandbox(config)\n\nresult = sandbox.run(\"sum(range(100))\")\nassert result == 4950\n```\n\n### Rust\n\nThe `#[tool]` macro is the easiest way to register tools. Write a normal function with doc comments, and the macro generates everything needed for registration and LLM documentation:\n\n```rust\nuse littrs::Sandbox;\nuse littrs_macros::tool;\n\n/// Get current weather for a city.\n///\n/// Args:\n///     city: The city name\n///     units: Temperature units (C or F)\n#[tool]\nfn get_weather(city: String, units: Option\u003cString\u003e) -\u003e String {\n    format!(\"{}: 22°{}\", city, units.unwrap_or(\"C\".into()))\n}\n\nlet mut sandbox = Sandbox::new();\nsandbox.add(get_weather::Tool);\n\nlet result = sandbox.run(r#\"get_weather(\"London\")\"#).unwrap();\n```\n\nThe `#[tool]` macro handles type conversion from `PyValue` automatically. `sandbox.add()` registers the tool with its full metadata.\n\nVariables persist across `run()` calls:\n\n```rust\nsandbox.run(\"x = 10\").unwrap();\nsandbox.run(\"y = 20\").unwrap();\nlet result = sandbox.run(\"x + y\").unwrap();\nassert_eq!(result, PyValue::Int(30));\n```\n\n#### Resource Limits\n\n```rust\nuse littrs::{Sandbox, Limits};\n\nlet mut sandbox = Sandbox::new();\nsandbox.limit(Limits {\n    max_instructions: Some(10_000),\n    max_recursion_depth: Some(50),\n});\n\nlet err = sandbox.run(\"while True: pass\").unwrap_err();\nassert!(err.to_string().contains(\"Instruction limit\"));\n```\n\nResource 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.\n\n#### Tool Documentation\n\n`describe()` auto-generates Python-style docs for all registered tools, suitable for embedding in an LLM system prompt:\n\n```rust\nlet docs = sandbox.describe();\n// def get_weather(city: str, units?: str) -\u003e str:\n//     \"\"\"Get current weather for a city.\"\"\"\n```\n\n#### Capturing Print Output\n\n```rust\nlet mut sandbox = Sandbox::new();\nlet output = sandbox.capture(r#\"\nfor i in range(5):\n    print(i)\n\"done\"\n\"#).unwrap();\n\nassert_eq!(output.output, vec![\"0\", \"1\", \"2\", \"3\", \"4\"]);\nassert_eq!(output.value, PyValue::Str(\"done\".to_string()));\n```\n\n#### Low-level Registration\n\nFor cases where the `#[tool]` macro isn't suitable, you can register closures directly:\n\n```rust\nuse littrs::{Sandbox, PyValue};\n\nlet mut sandbox = Sandbox::new();\n\nsandbox.register_fn(\"fetch_data\", |args| {\n    let id = args[0].as_int().unwrap_or(0);\n    PyValue::Dict(vec![\n        (PyValue::Str(\"id\".to_string()), PyValue::Int(id)),\n        (PyValue::Str(\"name\".to_string()), PyValue::Str(\"Example\".to_string())),\n    ])\n});\n```\n\n#### Imports \u0026 Built-in Modules\n\nUse `Sandbox::with_builtins()` to enable `json`, `math`, and `typing` modules:\n\n```rust\nuse littrs::{Sandbox, PyValue};\n\nlet mut sandbox = Sandbox::with_builtins();\n\nlet result = sandbox.run(r#\"\nimport json\ndata = json.loads('{\"name\": \"Alice\", \"score\": 95}')\ndata[\"score\"]\n\"#).unwrap();\nassert_eq!(result, PyValue::Int(95));\n```\n\nRegister custom modules with `.module()`:\n\n```rust\nuse littrs::{Sandbox, PyValue};\n\nlet mut sandbox = Sandbox::new();\nsandbox.module(\"config\", |m| {\n    m.constant(\"version\", PyValue::Str(\"1.0\".into()));\n    m.function(\"get_flag\", |_args| PyValue::Bool(true));\n});\n\nlet result = sandbox.run(\"import config; config.version\").unwrap();\nassert_eq!(result, PyValue::Str(\"1.0\".into()));\n```\n\n#### File Mounting\n\nMount host files into the sandbox for controlled file I/O:\n\n```rust\nuse littrs::{Sandbox, PyValue};\n\nlet mut sandbox = Sandbox::new();\nsandbox.mount(\"data.json\", \"./data/input.json\", false);      // read-only\nsandbox.mount(\"output.txt\", \"./output/result.txt\", true);     // read-write\n\nsandbox.run(r#\"\nf = open(\"data.json\")\ncontent = f.read()\nf.close()\n\nf = open(\"output.txt\", \"w\")\nf.write(\"processed\")\nf.close()\n\"#).unwrap();\n\nlet files = sandbox.files();  // {\"output.txt\": \"processed\"}\n```\n\nUnmounted paths raise `FileNotFoundError`; writing to read-only mounts raises `PermissionError`. Both are catchable inside the sandbox with `try`/`except`.\n\n## Alternatives\n\nLittrs is designed for one specific use case: **running code written by AI agents safely and cheaply**. It trades language completeness for simplicity, speed, embeddability, and zero infrastructure requirements.\n\n| Tech | Security | Start latency | Embeddable | Resource limits | Tool registration | WASM isolation | Setup |\n|---|---|---|---|---|---|---|---|\n| **Littrs** | strict (no ambient access) | ~1ms | Rust, Python | instruction + recursion caps | built-in | built-in | `cargo add` / `pip install` |\n| Docker | good (container isolation) | ~200ms | no (separate process) | cgroups | roll your own | no | daemon + images |\n| Pyodide | poor (JS sandbox leaks) | ~2800ms | JS only | hard to enforce | roll your own | host-level only | WASM runtime + 12MB |\n| Monty | strict | \u003c0.1ms | Rust, Python, JS | memory + time + allocations | built-in | no | `pip install` |\n| Sandboxing services | strict (managed) | ~1000ms | no (API call) | service-managed | API-based | service-managed | API keys + network |\n| `exec()` / subprocess | **none** | ~0.1ms | Python only | none | none | no | none |\n\n*Comparison table adapted from [Monty](https://github.com/pydantic/monty).*\n\n**Why Littrs over Docker/services?** Zero infrastructure. No daemon, no containers, no network calls, no API keys. Just a library you import. Ideal for edge deployments, embedded systems, or anywhere you can't run Docker.\n\n**Why Littrs over `exec()`?** Security. `exec()` gives LLM-generated code full access to your filesystem, network, and environment. Littrs gives it access to nothing except the tools you explicitly register.\n\n**Why Littrs over Pyodide?** Startup speed and server-side safety. Pyodide takes seconds to cold-start and wasn't designed for server-side isolation — Python code can escape into the JS runtime.\n\n**Why Littrs over Monty?** Developer experience. Littrs provides a cleaner API — `@sandbox.tool` to register a function, `sandbox(code)` to run it, `sandbox[\"x\"] = val` to inject variables. No boilerplate, no separate input/output declarations, no configuration objects. It also includes built-in WASM isolation for stronger sandboxing when you need it.\n\n## Citation\n\nIf you use Littrs in your research, please cite it as:\n\n```bibtex\n@software{littrs,\n  title = {Littrs: A Minimal, Secure Python Sandbox for AI Agents},\n  author = {Chonkie Inc.},\n  url = {https://github.com/chonkie-inc/littrs},\n  license = {Apache-2.0},\n  year = {2025-2026}\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchonkie-inc%2Flittrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchonkie-inc%2Flittrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchonkie-inc%2Flittrs/lists"}