> ## Documentation Index
> Fetch the complete documentation index at: https://simplellmfunc.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# PyRepl

> Persistent Python REPL with runtime primitives and streaming output

# PyRepl

PyRepl is a persistent IPython REPL running in a subprocess. It gives the model a continuous execution environment where variables persist across calls and runtime primitives are accessible without imports.

In 0.8.1, `PyRepl` remains the public facade while its internals are split into focused components: worker lifecycle (`pyrepl_worker_client.py`), execute/reset orchestration (`pyrepl_execution.py`), primitive host integration (`pyrepl_primitive_host.py`), tool factory/output formatting (`pyrepl_tools.py`), audit logging (`pyrepl_audit.py`), and input bridging (`pyrepl_input_bridge.py` / `pyrepl_input_mixin.py`). This is an internal architecture cleanup; user-facing usage stays the same.

## Core Properties

* **Persistent state** — Variables defined in one `execute_code` call are available in the next
* **Isolated process** — Runs in a separate subprocess (multiprocessing spawn). Crashes don't kill the main process
* **Runtime injection** — The `runtime` object is globally available. No imports needed
* **Streaming output** — stdout/stderr stream in real-time via custom events
* **Image artifacts** — images produced by `display(Image(...))` or image-rich last expressions are returned to the model as multimodal tool results
* **Timeout protection** — Default 600s per execution. Configurable

## Setup

```python theme={null}
from SimpleLLMFunc.builtin import PyRepl

repl = PyRepl(working_directory="/path/to/workspace")

# Use as toolkit in an agent
@llm_chat(llm_interface=llm, toolkit=repl.toolset, stream=True)
async def agent(message: str, history: list | None = None):
    """A code-executing agent."""
    pass
```

## Tools Provided

### execute\_code

Run arbitrary Python code in the persistent REPL:

```python theme={null}
result = await execute_code(code="""
import math
x = math.sqrt(144)
print(f"Result: {x}")
x
""")
# Returns: {"stdout": "Result: 12.0\n", "return_value": "12.0", "execution_time_ms": ...}
```

Return value is the last expression's repr (like IPython).

### Image Output

When executed code produces image output, `execute_code` returns a multimodal tool result instead of flattening the image to text. The model receives the normal execution summary plus the image content.

Supported patterns include explicit display calls:

```python theme={null}
from IPython.display import Image, display

display(Image(filename="chart.png"))
```

And image-rich last expressions:

```python theme={null}
from IPython.display import Image

Image(filename="chart.png")
```

For generated plots, saving the figure and returning an image payload is the clearest pattern:

```python theme={null}
import matplotlib.pyplot as plt
from SimpleLLMFunc.type import ImgPath

plt.plot([1, 2, 3])
path = "chart.png"
plt.savefig(path)
ImgPath(path)
```

Direct `PyRepl.execute(...)` calls expose captured images in the returned `artifacts` list. The `execute_code` tool converts those artifacts into `ImgPath` / `ImgUrl` multimodal returns for the agent loop.

### reset\_repl

Clear all user variables but keep runtime backends:

```python theme={null}
await reset_repl()
# After this: `x` is gone, but `runtime.selfref.context.inspect()` still works
```

## Runtime Namespace

Inside `execute_code`, the `runtime` object provides:

```python theme={null}
# Meta-primitives (always available)
runtime.list_primitives()
runtime.list_primitive_specs()
runtime.get_primitive_spec("selfref.context.compact")
runtime.list_backends()

# SelfRef primitives (when selfref is active)
runtime.selfref.context.inspect()
runtime.selfref.context.remember("important fact")
runtime.selfref.context.forget("exp_001")
runtime.selfref.context.compact(goal=..., instruction=..., ...)
runtime.selfref.fork.spawn(task=..., instruction=...)
runtime.selfref.fork.gather_all()

# Custom pack primitives (if installed)
runtime.metrics.increment_counter("files_read")
```

## Streaming Output

PyRepl emits custom events for real-time output:

| Event Name      | Data              | When              |
| --------------- | ----------------- | ----------------- |
| `kernel_stdout` | `{"text": "..."}` | Each stdout flush |
| `kernel_stderr` | `{"text": "..."}` | Each stderr flush |

Consume in your event handler:

```python theme={null}
from SimpleLLMFunc.hooks import is_event_yield, CustomEvent

async for output in agent("run some code", history):
    if is_event_yield(output):
        if isinstance(output.event, CustomEvent):
            if output.event.event_name == "kernel_stdout":
                print(output.event.data["text"], end="")
```

## Output Truncation

If a tool result exceeds \~20,000 tokens:

1. Full output is written to a temporary file
2. Truncated version (first \~4,096 tokens) + file path is returned to the model
3. The model can use `read_file` to access specific parts

Enable per-agent with `_too_long_to_file=True`.

## Working Directory

```python theme={null}
repl = PyRepl(working_directory="/path/to/project")
```

The REPL starts in this directory. `os.getcwd()` inside execute\_code returns this path.

## Installing Custom Primitive Packs

```python theme={null}
from SimpleLLMFunc.runtime import PrimitivePack

my_pack = PrimitivePack(namespace="mytools", backend=..., primitives=[...])
repl.install_primitive_pack(my_pack)
```

After installation, `runtime.mytools.*` is available in execute\_code.

## SelfRef Integration

When using `self_reference_key` on the agent, the framework automatically:

1. Creates a SelfReference backend
2. Builds the selfref primitive pack
3. Installs it in the PyRepl instance
4. Makes `runtime.selfref.*` available

```python theme={null}
@llm_chat(
    llm_interface=llm,
    toolkit=repl.toolset,
    self_reference_key="agent_main",
)
async def agent(message: str, history: list | None = None):
    """Agent with code execution + self-reference."""
    pass
```

## Practical Pattern: CodeAct Agent

The "CodeAct" pattern uses PyRepl as the primary action surface — the model writes Python to accomplish tasks instead of using separate tools for each operation:

```python theme={null}
repl = PyRepl(working_directory=workspace)
file_tools = FileToolset(workspace).toolset

@llm_chat(
    llm_interface=llm,
    toolkit=[*repl.toolset, *file_tools],
    self_reference_key="agent_main",
    stream=True,
)
async def coding_agent(message: str, history: list | None = None):
    """
    Solve coding tasks by writing and executing Python.
    Use execute_code for inspection, computation, and verification.
    Use file tools for reading and editing project files.
    Use runtime.selfref.context.compact(...) when context grows large.
    """
    pass
```

→ [API Reference: Builtins](/api/builtins)
