Metadata-Version: 2.5
Name: levain-runtime
Version: 0.1.25
Summary: Self-hosted runtime for Levain agents: run an agent's nodes on your own machine or container while the platform drives the recipe
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: certifi>=2026.10.19
Requires-Dist: httpx==0.28.1
Requires-Dist: levain-sdk[graph,models,review]
Requires-Dist: pydantic==2.13.4
Requires-Dist: starlette==1.10.0
Requires-Dist: typer==0.27.3
Requires-Dist: uvicorn==0.54.0
Description-Content-Type: text/markdown

# levain-runtime

Run a [Levain](https://levainlabs.com) agent on your own machine or in your own
container image. The platform keeps the recipe, its versions, the run history
and the improvement loop; this runtime executes the agent's nodes where your
data, tools and dependencies live.

The runtime never accepts inbound connections. It opens one outbound tunnel to
the platform and serves node execution over it. Model calls either go through
the platform (metered as usual) or use the Claude login present on the runtime
host, as configured per runtime.

## Install

```sh
uv tool install levain-runtime
```

## Set up

```sh
# Run nodes directly on this host:
levain-runtime init --mode direct

# Direct mode serving four sessions at once (Linux with bubblewrap):
levain-runtime init --mode direct --sessions 4

# Or run each session in a container built from your own image:
levain-runtime init --mode docker --image ghcr.io/you/agent-runtime:latest
```

Direct mode links `/workspace`, `/recipes`, `/sdk` and `/sandbox` to the
runtime's home directory (`~/.levain/runtime`); the command prints and runs the
`ln` commands, with `sudo` when `/` is not writable. On macOS the links come
from `/etc/synthetic.conf`: `init` writes the entries and asks for one restart.

A docker image needs Python 3.12 or later with `levain-sdk[review,models]`
installed, plus whatever your recipes import.

## Register and run

```sh
export LEVAIN_API_KEY=...   # a workspace API key
levain-runtime register --name workstation --model-auth local
levain-runtime up
```

`register` creates the runtime in your workspace and stores its credentials
here. If the runtime was created elsewhere (the Levain app or API), pass the
credentials it returned instead:
`levain-runtime connect --credentials runtime-credentials.json`.

`up` starts the local node server, the tunnel client and a heartbeat so the
platform can show whether the runtime is online. It fetches the current tunnel
settings from the platform on every start. `levain-runtime status` prints the
configuration and the sessions on this runtime.

## Upgrade

A platform release can need a newer runtime: the node and turn entrypoints
under `/sandbox` ship with this package, and runs wait while the runtime is too
old for them. The Runtimes page in the Levain app, and `runner_version` in
`GET /api/v1/runtimes`, show the version each runtime runs;
`levain-runtime --version` prints it here.

```sh
uv tool upgrade levain-runtime
systemctl --user restart levain-runtime  # or however `up` runs on this host
```

`up` rewrites `/sandbox` from the installed release on every start, so an
upgrade needs no new `init`. Restarting ends the nodes the runtime was running,
so upgrade between runs.

## How it works

Each agent session gets a workspace directory under the runtime home
(`sessions/<id>/workspace`, or `agents/<id>/workspace` for agents with a
persistent workspace). The platform delivers the recipe and SDK into `/recipes`
and `/sdk`, then runs each node as a short-lived process with the session's
environment. Everything a node prints is streamed back and lands in the run's
logs on the platform.

Direct mode serves one session at a time by default: `/workspace` points at
the active session. With `--sessions N` it serves up to N at once, each node
running under bubblewrap with its session's folders mounted at `/workspace`,
`/recipes/active` and `/sdk/active`; the real workspace path is in
`LEVAIN_HOST_WORKSPACE` for tools outside the sandbox such as Docker. Docker
mode starts one container per session and has no such limit.

## Sessions and your Claude login

In direct mode an agent's turns run in the session's workspace directory with
this machine's Claude login and config dir (`CLAUDE_CONFIG_DIR`, or
`~/.claude`). Claude Code files the agent's sessions where it files yours, under
that directory's entry, so from the workspace directory `claude --resume` lists
the agent's sessions next to your own and continues any of them.
`levain-runtime status` prints each session's workspace and the Claude session
it continues.

The map from a Levain session to its Claude session lives in
`<config dir>/levain-sessions/`. To have an agent continue a session you started
yourself in its workspace directory:

```sh
levain-runtime adopt <levain session id> <claude session id>
```

The agent's next turn resumes that session. Docker mode copies your login into
each container instead; its sessions stay in the container.

## Waiting sessions

In direct mode, when an agent's turn stops to wait, the platform gives the
session's slot back and tells the runtime what the session waits on. The
runtime keeps one file per waiting session, `waiting/<session id>.json` under
its home, so a process on this machine can react without polling the API:

```json
{
  "session_id": "…",
  "agent_id": "…",
  "workspace_id": "…",
  "run_id": "…",
  "reason": "user_input",
  "payload": { "question": "…" },
  "since": "2026-10-29T08:12:44.018311+00:00"
}
```

`reason` is `user_input` (a person's reply), `child_agent` (another agent's
run) or `external` (an outside event); `payload` is what the agent passed with
the wait. Each file is renamed into place, so a watcher of `*.json` only sees
whole files. It is removed when the session resumes, is provisioned again or
is deleted; a restart of the runtime keeps it.
