Skip to content

Types API

FileInfo

pydantic_ai_backends.types.FileInfo

Bases: _FileInfoFields

Information about a file or directory.

modified_at is an ISO 8601 timestamp, present when the backend can report one: StateBackend records it on every write, filesystem-backed listings take st_mtime, and the remote wire carries it end to end. A backend that derives its listing from shell ls output has no reliable timestamp to give, so the key is absent there — read it with .get("modified_at") and treat a missing key as unknown, never as "just now".

Source code in src/pydantic_ai_backends/types.py
Python
class FileInfo(_FileInfoFields, total=False):
    """Information about a file or directory.

    `modified_at` is an ISO 8601 timestamp, present when the backend can report
    one: `StateBackend` records it on every write, filesystem-backed listings
    take `st_mtime`, and the remote wire carries it end to end. A backend that
    derives its listing from shell `ls` output has no reliable timestamp to
    give, so the key is absent there — read it with `.get("modified_at")` and
    treat a missing key as unknown, never as "just now".
    """

    modified_at: str | None
Python
from pydantic_ai_backends import FileInfo

# Example
file_info: FileInfo = {
    "name": "app.py",
    "path": "/workspace/app.py",
    "is_dir": False,
    "size": 1234,
    "modified_at": "2026-08-16T12:00:00+00:00",  # absent when the backend cannot report one
}

FileData

pydantic_ai_backends.types.FileData

Bases: _FileDataFields

One file as StateBackend stores it.

A dictionary of these is a JSON document, and callers depend on that: the backend's whole use beyond a single process is that a host can persist StateBackend.files and hand it back to StateBackend(files=...) later. That guarantee is what encoding exists for.

Text is stored as content, split on newlines, with no encoding key. Content that is not valid UTF-8 — a PNG, a zip — is stored base64 in a single content entry with encoding="base64", and only the backend's own readers ever see the difference.

It used to be lines of text unconditionally, with bytes decoded using errors="surrogateescape". That round-tripped correctly in Python, which is exactly why it survived: the lone surrogates it produces are re-encoded by read_bytes into the original bytes, and json.dumps emits them without complaint while json.loads reads them back. Nothing stricter accepts them. PostgreSQL jsonb rejects an unpaired escape outright, a text column cannot hold one because it is not valid UTF-8, and any non-Python reader of the same document refuses it. So a workspace was serialisable right up until an agent wrote an image into it, and the failure landed at the storage layer rather than at the write that caused it.

A document written before encoding existed still loads: no key means text, and the surrogates such a document may contain are encoded back to their original bytes as they always were.

Source code in src/pydantic_ai_backends/types.py
Python
class FileData(_FileDataFields, total=False):
    """One file as `StateBackend` stores it.

    **A dictionary of these is a JSON document**, and callers depend on that:
    the backend's whole use beyond a single process is that a host can persist
    `StateBackend.files` and hand it back to `StateBackend(files=...)` later.
    That guarantee is what `encoding` exists for.

    Text is stored as `content`, split on newlines, with no `encoding` key.
    Content that is not valid UTF-8 — a PNG, a zip — is stored base64 in a
    single `content` entry with `encoding="base64"`, and only the backend's own
    readers ever see the difference.

    It used to be lines of text unconditionally, with bytes decoded using
    `errors="surrogateescape"`. That round-tripped correctly *in Python*, which
    is exactly why it survived: the lone surrogates it produces are re-encoded
    by `read_bytes` into the original bytes, and `json.dumps` emits them without
    complaint while `json.loads` reads them back. Nothing stricter accepts them.
    PostgreSQL `jsonb` rejects an unpaired escape outright, a `text` column
    cannot hold one because it is not valid UTF-8, and any non-Python reader of
    the same document refuses it. So a workspace was serialisable right up until
    an agent wrote an image into it, and the failure landed at the storage layer
    rather than at the write that caused it.

    A document written before `encoding` existed still loads: no key means text,
    and the surrogates such a document may contain are encoded back to their
    original bytes as they always were.
    """

    encoding: Literal["base64"]
    """Present only when `content` holds base64 rather than lines of text."""

encoding instance-attribute

Present only when content holds base64 rather than lines of text.

Python
from pydantic_ai_backends import FileData

# Text: lines, and no encoding key.
file_data: FileData = {
    "content": ["line 1", "line 2", "line 3"],
    "created_at": "2024-01-15T10:30:00Z",
    "modified_at": "2024-01-15T11:00:00Z",
}

# Content that is not valid UTF-8: one base64 entry, marked.
image_data: FileData = {
    "content": ["iVBORw0KGgo..."],
    "created_at": "2024-01-15T10:30:00Z",
    "modified_at": "2024-01-15T11:00:00Z",
    "encoding": "base64",
}

A dictionary of these is always a JSON document, which is what lets a host persist a StateBackend and restore it:

Python
import json

stored = json.dumps(backend.files, ensure_ascii=False)
restored = StateBackend(files=json.loads(stored))

WriteResult

pydantic_ai_backends.types.WriteResult dataclass

Result of a write operation.

Source code in src/pydantic_ai_backends/types.py
Python
@dataclass
class WriteResult:
    """Result of a write operation."""

    path: str | None = None
    error: str | None = None
Python
from pydantic_ai_backends import WriteResult

# Success
result = WriteResult(path="/workspace/app.py")

# Error
result = WriteResult(error="Permission denied")

EditResult

pydantic_ai_backends.types.EditResult dataclass

Result of an edit operation.

Source code in src/pydantic_ai_backends/types.py
Python
@dataclass
class EditResult:
    """Result of an edit operation."""

    path: str | None = None
    error: str | None = None
    occurrences: int | None = None
Python
from pydantic_ai_backends import EditResult

# Success
result = EditResult(path="/workspace/app.py", occurrences=3)

# Error
result = EditResult(error="String not found")

ExecuteResponse

pydantic_ai_backends.types.ExecuteResponse dataclass

Response from command execution in a sandbox.

Source code in src/pydantic_ai_backends/types.py
Python
@dataclass
class ExecuteResponse:
    """Response from command execution in a sandbox."""

    output: str
    exit_code: int | None = None
    truncated: bool = False
Python
from pydantic_ai_backends import ExecuteResponse

# Example
response = ExecuteResponse(
    output="Hello, World!\n",
    exit_code=0,
    truncated=False,
)

GrepMatch

pydantic_ai_backends.types.GrepMatch

Bases: TypedDict

A single grep match result.

Source code in src/pydantic_ai_backends/types.py
Python
class GrepMatch(TypedDict):
    """A single grep match result."""

    path: str
    line_number: int
    line: str
Python
from pydantic_ai_backends import GrepMatch

# Example
match: GrepMatch = {
    "path": "/workspace/app.py",
    "line_number": 42,
    "line": "def hello_world():",
}

RuntimeConfig

pydantic_ai_backends.types.RuntimeConfig

Bases: BaseModel

Configuration for a Docker runtime environment.

Describes a pre-configured execution environment so a DockerSandbox needs no manual package installation. Give it either a ready-made image, or a base_image plus packages to build from.

Example
Python
from pydantic_ai_backends import RuntimeConfig, DockerSandbox

# Custom runtime with ML packages
ml_runtime = RuntimeConfig(
    name="ml-env",
    description="Machine learning environment",
    base_image="python:3.12-slim",
    packages=["torch", "transformers", "datasets"],
)

sandbox = DockerSandbox(runtime=ml_runtime)
Source code in src/pydantic_ai_backends/types.py
Python
class RuntimeConfig(BaseModel):
    """Configuration for a Docker runtime environment.

    Describes a pre-configured execution environment so a `DockerSandbox` needs
    no manual package installation. Give it either a ready-made `image`, or a
    `base_image` plus `packages` to build from.

    Example:
        ```python
        from pydantic_ai_backends import RuntimeConfig, DockerSandbox

        # Custom runtime with ML packages
        ml_runtime = RuntimeConfig(
            name="ml-env",
            description="Machine learning environment",
            base_image="python:3.12-slim",
            packages=["torch", "transformers", "datasets"],
        )

        sandbox = DockerSandbox(runtime=ml_runtime)
        ```
    """

    name: str
    """Unique name for the runtime (e.g., "python-datascience")."""

    description: str = ""
    """Human-readable description of the runtime."""

    image: str | None = None
    """Ready-to-use Docker image (e.g., "myregistry/python-ds:v1")."""

    base_image: str | None = None
    """Base image to build upon (e.g., "python:3.12-slim")."""

    packages: list[str] = []
    """Packages to install (e.g., ["pandas", "numpy", "matplotlib"])."""

    package_manager: Literal["pip", "npm", "apt", "cargo"] = "pip"
    """Package manager to use for installation."""

    setup_commands: list[str] = []
    """Additional setup commands to run (e.g., ["apt-get update"])."""

    env_vars: dict[str, str] = {}
    """Environment variables to set in the container."""

    work_dir: str = "/workspace"
    """Working directory inside the container."""

    cache_image: bool = True
    """Whether to cache the built image locally."""

    run_as_uid: int | None = None
    """Build the image around an unprivileged user, and run containers as them.

    `None` — the default — runs as root, which is what a container does unless
    told otherwise. Naming a uid instead adds three things to the generated
    image: a real user with that id (so `whoami` and anything else calling
    `getpwuid` works), a home directory it owns, and a virtualenv at
    :data:`VENV_PATH` it owns, put first on `PATH`.

    The virtualenv is the part that makes this workable rather than merely
    safer. A non-root user cannot write to the interpreter's own
    `site-packages`, so without one an agent's first `pip install` fails — and
    `uv`, unlike pip, has no `--user` mode to fall back on, so it fails with no
    way forward at all. Owning a virtualenv means both simply work, and console
    scripts land somewhere already on `PATH`.

    Only meaningful with `base_image`: a ready-made `image` was not built with
    this user, so running it unprivileged would leave an agent unable to install
    anything system-wide.

    The uid has to match whoever owns the workspace mounted into the container,
    which is why it is a number rather than a name.
    """

name instance-attribute

Unique name for the runtime (e.g., "python-datascience").

description = '' class-attribute instance-attribute

Human-readable description of the runtime.

image = None class-attribute instance-attribute

Ready-to-use Docker image (e.g., "myregistry/python-ds:v1").

base_image = None class-attribute instance-attribute

Base image to build upon (e.g., "python:3.12-slim").

packages = [] class-attribute instance-attribute

Packages to install (e.g., ["pandas", "numpy", "matplotlib"]).

package_manager = 'pip' class-attribute instance-attribute

Package manager to use for installation.

setup_commands = [] class-attribute instance-attribute

Additional setup commands to run (e.g., ["apt-get update"]).

env_vars = {} class-attribute instance-attribute

Environment variables to set in the container.

work_dir = '/workspace' class-attribute instance-attribute

Working directory inside the container.

cache_image = True class-attribute instance-attribute

Whether to cache the built image locally.

run_as_uid = None class-attribute instance-attribute

Build the image around an unprivileged user, and run containers as them.

None — the default — runs as root, which is what a container does unless told otherwise. Naming a uid instead adds three things to the generated image: a real user with that id (so whoami and anything else calling getpwuid works), a home directory it owns, and a virtualenv at :data:VENV_PATH it owns, put first on PATH.

The virtualenv is the part that makes this workable rather than merely safer. A non-root user cannot write to the interpreter's own site-packages, so without one an agent's first pip install fails — and uv, unlike pip, has no --user mode to fall back on, so it fails with no way forward at all. Owning a virtualenv means both simply work, and console scripts land somewhere already on PATH.

Only meaningful with base_image: a ready-made image was not built with this user, so running it unprivileged would leave an agent unable to install anything system-wide.

The uid has to match whoever owns the workspace mounted into the container, which is why it is a number rather than a name.

Python
from pydantic_ai_backends import RuntimeConfig

# Custom runtime
runtime = RuntimeConfig(
    name="ml-env",
    base_image="python:3.12-slim",
    packages=["torch", "transformers"],
    env_vars={"PYTHONUNBUFFERED": "1"},
    work_dir="/workspace",
)