# Embedded Python (Monty)

> **Experimental.** Monty is an early-stage Python interpreter that may have
> undiscovered crash or security bugs. Resource limits are enforced by Monty's
> runtime. The integration should be treated as experimental.

Bashkit embeds the [Monty](https://github.com/pydantic/monty) Python interpreter,
a pure-Rust implementation of Python 3.12. Python runs entirely in-memory with
configurable resource limits and no host access.

**See also:**
- [Threat Model](./threat-model.md) - Security considerations (TM-PY-*)
- [Custom Builtins](./custom_builtins.md) - Writing your own builtins
- [Compatibility Reference](./compatibility.md) - Bash feature support
- [`specs/python-builtin.md`][spec] - Full specification

## Quick Start

Enable the `python` feature and register via builder:

```rust
use bashkit::Bash;

# #[tokio::main]
# async fn main() -> bashkit::Result<()> {
let mut bash = Bash::builder()
    .python()
    .env("BASHKIT_ALLOW_INPROCESS_PYTHON", "1")
    .build();

let result = bash.exec("python3 -c \"print('hello from Monty')\"").await?;
assert_eq!(result.stdout, "hello from Monty\n");
# Ok(())
# }
```

## Usage Patterns

### Inline Code

```bash
python3 -c "print(2 ** 10)"
# Output: 1024
```

### Expression Evaluation

When no `print()` is called, the last expression is displayed (REPL behavior):

```bash
python3 -c "2 + 2"
# Output: 4
```

### Script Files (from VFS)

```bash
cat > /tmp/script.py << 'EOF'
data = [1, 2, 3, 4, 5]
print(f"sum={sum(data)}, avg={sum(data)/len(data)}")
EOF
python3 /tmp/script.py
```

### Pipelines and Command Substitution

```bash
result=$(python3 -c "print(42 * 3)")
echo "Result: $result"

echo "print('piped')" | python3
```

## Virtual Filesystem (VFS) Bridging

Python `pathlib.Path` operations are bridged to Bashkit's virtual filesystem.
Files created by bash are readable from Python and vice versa.

### Bash → Python

```bash
echo "important data" > /tmp/shared.txt
python3 -c "
from pathlib import Path
content = Path('/tmp/shared.txt').read_text()
print(f'Got: {content.strip()}')
"
```

### Python → Bash

```bash
python3 -c "
from pathlib import Path
_ = Path('/tmp/result.txt').write_text('computed by python\n')
"
cat /tmp/result.txt
```

### Supported Path Operations

| Operation | Example |
|-----------|---------|
| Read text | `Path('f.txt').read_text()` |
| Read bytes | `Path('f.txt').read_bytes()` |
| Write text | `Path('f.txt').write_text('data')` |
| Write bytes | `Path('f.txt').write_bytes(b'data')` |
| Exists | `Path('f.txt').exists()` |
| Is file/dir | `Path('f.txt').is_file()`, `.is_dir()` |
| Mkdir | `Path('d').mkdir(parents=True, exist_ok=True)` |
| Delete | `Path('f.txt').unlink()` |
| List dir | `Path('.').iterdir()` |
| Stat | `Path('f.txt').stat().st_size` |
| Rename | `Path('old').rename('new')` |
| Env vars | `os.getenv('KEY')`, `os.environ` |

### Architecture

```text
Python code → Monty VM → OsCall(ReadText, path) → Bashkit VFS → resume
```

Monty pauses at filesystem operations, Bashkit bridges them to the VFS, then
resumes execution with the result (or a Python exception like `FileNotFoundError`).

## Resource Limits

Default limits prevent runaway Python code. Customize via `PythonLimits`:

```rust,no_run
use bashkit::{Bash, PythonLimits};
use std::time::Duration;

# fn main() {
let bash = Bash::builder()
    .python_with_limits(
        PythonLimits::default()
            .max_duration(Duration::from_secs(5))
            .max_memory(16 * 1024 * 1024)   // 16 MB
            .max_allocations(100_000)
            .max_recursion(50)
    )
    .build();
# }
```

| Limit | Default | Purpose |
|-------|---------|---------|
| Allocations | 1,000,000 | Heap allocation cap |
| Duration | 30 seconds | Execution timeout |
| Memory | 64 MB | Heap memory cap |
| Recursion | 200 | Call stack depth |

## LLM Tool Integration

When using `BashTool` for AI agents, call `.python()` on the tool builder:

```rust,no_run
use bashkit::{BashTool, Tool};

# fn main() {
let tool = BashTool::builder()
    .python()
    .build();

// help() and system_prompt() automatically document Python limitations
let help = tool.help();  // Includes a Markdown Notes section with Python hints
# }
```

The builtin's `llm_hint()` is automatically included in the tool's documentation,
so LLMs know not to generate code using `open()`, HTTP requests, or classes.

## Limitations

**No `open()` builtin.** Monty does not implement Python's `open()`. Use `pathlib.Path` instead:

```python
# Won't work:
# f = open('data.txt')

# Use instead:
from pathlib import Path
content = Path('data.txt').read_text()
```

**No HTTP/network.** No `socket`, `urllib`, `requests`, or `http.client` modules.
Monty has no network primitives and no OsCall variants for network operations.

**No classes.** Class definitions are not yet supported by Monty (planned upstream).

**No third-party imports.** Only builtin modules (`sys`, `typing`, `os`, `pathlib`,
`math`, `json`, `datetime`) are available. No `pip install`, no `import numpy`.

**No `re` module.** Disabled due to catastrophic backtracking DoS risk in
untrusted code execution.

**No `str.format()`.** Use f-strings instead: `f"value={x}"` not `"value={}".format(x)`.

## Security

All Python execution runs in a virtual environment:

- **No host filesystem access** — all paths resolve through the VFS
- **No network access** — no sockets, HTTP, or DNS
- **No process spawning** — no `os.system()`, `subprocess`, or `__import__('os')`
- **Resource limited** — allocation, time, memory, and recursion caps
- **Path traversal safe** — `../..` is resolved by VFS path normalization

See threat IDs TM-PY-001 through TM-PY-029 in the [threat model](./threat-model.md).

[spec]: https://github.com/everruns/bashkit/blob/main/specs/python-builtin.md
