A Python toolkit that orchestrates AI coding agents inside sandboxes via run().
Project description
pysolated
A Python toolkit that orchestrates AI coding agents inside sandboxes to enable automated Ralph like loops in sequence or in parralel for example to automate the completion of tickets on a backlog marked as not needing humans in the loop, or to spin up and down isolated agents to check code in continuous integration loops.
A Python
reimagining of Sandcastle; see
CONTEXT.md for the vocabulary and docs/ for the PRD and
ADRs.
Why pysolated
There are alternatives that may be better in specific circumstances. To understand when pysolated is appropriate consider when not to use platform specific tools like bubblewrap and seatbelt or AI agent specific tools like Claude's /sandbox.
If you are using multiple different coding agents such as Claude and Codex and need to run on multiple different platforms such as Linux, MacOS or cloud hosted sandbox providers, and you want a consistent process in all cases, the pysolated will do the trick.
Why not use Sandcastle? If you are comfortable with Node or need to programatically instantiate agent loops from Typescript/Javascript code then Sancastle is the better choice. If you are more comfortable with Python or need to orchestrate agent loops from Python code, pysolate is functionally equivalent at the time of publication but built in Python
Status
v1 iteration lifecycle: two agent providers (claude_code and codex), driven
through run() and the pysolated run CLI. The run loops up to max_iterations,
stops early on a completion signal, enforces an idle timeout and a
post-signal completion grace window, reports the commits the agent
made, supports abort via an asyncio.Event (or Ctrl-C from the CLI), and
optionally extracts a schema-validated structured output payload from the
agent's prose. Three sandbox providers ship: no_sandbox (no isolation —
host subprocess), podman (rootless container with a same-path repo bind
mount — real isolation, see below), and docker (Podman's sibling for
Docker-only hosts; mirrors Podman everywhere the two engines agree).
pysolated init scaffolds a ready-to-run config directory for any of the
agent × sandbox combos, so a new project reaches a sandboxed run without
hand-writing the driver or the Containerfile.
Getting started
pysolated init scaffolds a config directory (.pysolated/) into your repo
so a project goes from zero to a sandboxed agent without hand-writing the glue.
It's an interactive wizard: it prompts for any choice you don't pass as a flag,
then writes the driver, the prompt template, the Containerfile, and the env
files for the chosen agent × sandbox combo.
# interactive — prompts for the agent and the sandbox
uv run pysolated init
# or pass both choices up front (fully non-interactive)
uv run pysolated init --agent claude-code --sandbox podman
The agent axis is claude-code or codex; the sandbox axis is podman or
docker — any of the four combos. init writes a .pysolated/ containing:
| File | What it is |
|---|---|
main.py |
The driver — a single configured run() call you edit to taste. Loads credentials from .env and passes them explicitly to the sandbox. |
prompt.md |
A skeleton prompt template ({{KEY}} substitution + !`cmd` expansion). |
Containerfile / Dockerfile |
The image the sandbox runs. Containerfile for podman, Dockerfile for docker. |
.env.example |
The credential keys the chosen agent needs (e.g. CLAUDE_CODE_OAUTH_TOKEN for claude-code, OPENAI_API_KEY for codex). |
.gitignore |
Ignores .env, so credentials never get committed. |
init then prints the remaining three steps:
cp .pysolated/.env.example .pysolated/.env # then fill in your credentials
uv run pysolated podman build-image # build the scaffolded image
python .pysolated/main.py # run the driver
The agent runs inside the sandbox against your repo (bind-mounted at the same
path) and its commits land back on the host. Because podman keep-id and docker
host-UID alignment match the in-container user to your host user, files the
agent writes are owned by you with no EACCES. init refuses to overwrite an
existing .pysolated/. See Project scaffolding (init)
for the full flag reference.
Library
import asyncio
from pysolated import run, claude_code, no_sandbox
async def main():
result = await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt="say hi",
)
print(result.stdout)
print(result.branch, result.usage)
asyncio.run(main())
run() returns a frozen RunResult (iterations, stdout, branch, usage,
completion_signal, commits, output, log_file_path). The inline prompt
is sent to the agent verbatim — no substitution or expansion. Loop and timer
behavior is configurable:
result = await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt="Refactor X. Emit DONE when finished.",
max_iterations=5, # default 1
completion_signal="DONE", # str or list[str]; default <promise>COMPLETE</promise>
idle_timeout_seconds=600, # fail if no output for this long
completion_timeout_seconds=60, # grace window after the signal is seen
)
Agent providers
Two agent providers ship today. Both satisfy the same AgentProvider seam
(ADR 0001), so swapping between
them is one constructor change in your run() call.
claude_code(model, *, permission_mode=None) — drives the claude CLI.
model defaults to claude-opus-4-7. Always emits
--dangerously-skip-permissions; permission_mode maps to Claude's
--permission-mode.
codex(model, *, effort=None, env=None) — drives the codex CLI. model
is required (Codex has no default). effort (low|medium|high|xhigh) tunes
reasoning effort via a model_reasoning_effort TOML override. Always emits
--dangerously-bypass-approvals-and-sandbox, symmetric with claude_code's
skip-permissions default.
from pysolated import run, no_sandbox
from pysolated.agents.codex import codex
await run(
agent=codex("gpt-5-codex", effort="high"),
sandbox=no_sandbox(),
prompt="say hi",
)
Both expose the same effort/permission_mode mismatch as errors: effort is
rejected by claude_code, and permission_mode is rejected by codex.
Sandbox providers
Three sandbox providers ship today; they all implement the same factory +
live-handle seam (ADR 0003),
so swapping between them is one constructor change in your run() call.
no_sandbox() — no isolation. The agent is a host subprocess that touches
your real working directory. Right for trusted runs in throwaway worktrees;
wrong for anything you wouldn't paste into your shell.
podman(image=…) — a long-lived rootless container as the isolation
boundary. run() starts the container once, runs every command in it via
podman exec, and removes it (podman rm -f) when the run exits — success,
failure, idle timeout, or Ctrl-C.
from pysolated import run, claude_code, podman
await run(
agent=claude_code("claude-opus-4-7"),
sandbox=podman(
image="pysolated-agent:latest",
env={"ANTHROPIC_API_KEY": "sk-..."}, # MUST pass credentials explicitly
),
prompt="say hi",
)
Key behaviours:
- Same-path repo bind mount (ADR 0004).
The host repo is mounted at the identical path inside the container with
the default
:zSELinux label. Combined with--userns=keep-id:uid=N,gid=N--user N:N, files appear owned by the in-container user and the orchestrator's hostcwdpasses throughpodman exec -wunchanged — no chown step, no gitsafe.directoryconfiguration.
- Credentials are explicit. Unlike
no_sandbox, the container does not inherit your host shell's environment. Anything the agent needs (ANTHROPIC_API_KEY,GH_TOKEN, …) goes throughenv=or a mounted file. This is the deliberate isolation surface, not a configuration miss. execis argv passthrough (ADR 0001). Nosh -cwrapper inside the container; the argv built by the agent provider runs as-is.- Cancellation kills the
podman execclient.podman rm -ffromclose()is the true kill switch for any in-container process the client left behind; anatexitbackstop catches the rare abnormal-exit case.
The image contract the provider relies on:
- A user/group exists at
container_uid:container_gid(default1000:1000), so keep-id maps host ↔ container ownership without a chown step. gitand the agent CLI (claudeforclaude_code) are onPATH.- The user has a writable
HOME. The provider injectsHOME=/home/agentby default; override viaenv={"HOME": "..."}.
A missing image is caught up front: create() runs podman image inspect <image> as a preflight and raises PodmanImageNotFoundError with a clear
message naming pysolated podman build-image so the fix is one command away.
Knobs on podman(...):
| Option | Default | Description |
|---|---|---|
image |
pysolated:<sanitized-host-dirname> |
The image to run. Same name pysolated podman build-image produces, so podman() and the CLI line up out of the box. |
env |
{} |
-e pairs at podman run. Provider env wins over the HOME=/home/agent default. |
userns |
"keep-id" |
--userns=keep-id:uid=N,gid=N + paired --user N:N. Pass None for raw Podman defaults. |
container_uid / container_gid |
1000 / 1000 |
uid:gid for --user and keep-id. |
selinux_label |
"z" |
Mount label (z shared, Z private). Pass None for no label. |
mounts |
[] |
Extra Mounts appended after the repo bind mount. See below. |
cpus |
None |
--cpus N (fractional ok, e.g. 1.5). Omitted when None. |
Custom mounts. Each Mount(host_path, sandbox_path, readonly=False) is
appended as a -v host:sandbox[:opts] entry on podman run. host_path is
tilde-expanded against the host $HOME and, if relative, resolved against
the host cwd; the resolved path must exist at create() time or the
provider fails fast with Mount host_path does not exist. sandbox_path
must be absolute — there is no sandbox-side ~ expansion. The ro and
SELinux-label options are composed through the same formatter as the repo
mount, so Mount(..., readonly=True) with selinux_label="z" renders as
…:ro,z.
Caveat: the sandbox-side parent directory must already exist in the image. Mounting a single file whose parent dir is absent will fail — auto-creating parent dirs for file mounts is tracked in
docs/futures/features.md.
from pysolated import podman, Mount
sandbox = podman(
image="pysolated-agent:latest",
env={"ANTHROPIC_API_KEY": "sk-..."},
mounts=[
Mount(host_path="~/.config/gh", sandbox_path="/home/agent/.config/gh"),
Mount(host_path="./data", sandbox_path="/data", readonly=True),
],
cpus=1.5,
)
Image lifecycle. pysolated podman build-image runs podman build -f Containerfile -t pysolated:<sanitized-host-dirname> . against the host cwd;
--file <path> overrides the Containerfile and --image <tag> overrides the
derived tag. pysolated podman remove-image is the matching podman rmi.
Memory limits and the other podman run knobs (--network, --group-add,
--device) are tracked in
docs/futures/features.md and the committed
roadmap there.
docker(image=…) — a long-lived Docker container, sibling to podman.
Mirrors the Podman provider everywhere the two engines agree: same-path repo
bind mount + :z label, argv-passthrough exec (no sh -c), docker rm -f
on close with the same idempotent + atexit-backed teardown, HOME=/home/agent
plus provider env (provider wins, no host os.environ forward), and the
same mounts / cpus knobs through the shared volume-spec builder.
The defining divergence is UID handling, because Docker has no
--userns=keep-id (ADR 0005):
container_uid/container_giddefault to the host UID/GID (resolved in thedocker()factory; falls back to1000whereos.getuidis unavailable). Host-UID alignment is the whole point — aDocker(...)provider isn't reproducible across hosts the wayPodman(container_uid=1000)is.--user N:Nis always emitted ondocker run— there is nousernsfield and no opt-out, because alignment is a single coupled mechanism and disabling it only reintroduces the silentEACCESit prevents.
from pysolated import run, claude_code, docker
await run(
agent=claude_code("claude-opus-4-7"),
sandbox=docker(
image="pysolated-agent:latest",
env={"ANTHROPIC_API_KEY": "sk-..."}, # MUST pass credentials explicitly
),
prompt="say hi",
)
The image contract is heavier than Podman's because --user,
pysolated docker build-image build-args, and the image preflight all depend
on it. The user-provided Dockerfile must:
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g $AGENT_GID <user> && \
usermod -o -u $AGENT_UID -g $AGENT_GID -d /home/agent -m -l agent <user>
USER ${AGENT_UID}:${AGENT_GID}
The -o flag lets alignment succeed when the host UID/GID collides with one
already in the base image; the numeric USER is what makes the
{{.Config.User}} preflight check parseable. git + the agent CLI on PATH,
writable HOME=/home/agent.
A missing image is caught up front: create() runs docker image inspect <image> as a preflight and raises DockerImageNotFoundError naming
pysolated docker build-image. A failed docker run raises
DockerLaunchError.
Prompt templates
A prompt template lives in a file and is resolved before the agent runs.
Pass it via prompt_file= (and prompt_args= for the values). prompt and
prompt_file are mutually exclusive; exactly one is required.
# prompts/refactor.txt
# Refactor the {{area}} module on branch {{branch}}.
# Most recent commit: !`git log -1 --oneline`
# Emit DONE when finished.
result = await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt_file="prompts/refactor.txt",
prompt_args={"area": "auth"}, # `branch` is built-in — don't pass it
completion_signal="DONE",
)
Two stages run in order, both via the sandbox seam (so a Docker sandbox later will resolve prompts inside the container too):
- Argument substitution — every
{{KEY}}is replaced.prompt_argsoverlay built-in arguments; for v1 the only built-in isbranch(the current git branch, injected automatically). Validation is strict:- Passing a
prompt_argskey that collides with a built-in raisesPromptArgumentErrorso you can't silently shadow framework values. - A
{{KEY}}with no matching argument raisesPromptArgumentErrorso a typo never reaches the agent as the literal{{KEY}}token.
- Passing a
- Prompt expansion — every
!`command`is replaced by the command's stdout, evaluated via the sandbox seam. A non-zero exit raisesPromptExpansionErrorimmediately, so a broken command never produces a silently-truncated prompt.
Inline prompts skip both stages. A literal {{KEY}} or !`cmd` in
an inline string is sent through verbatim — nothing is substituted or
executed. Passing prompt_args alongside an inline prompt raises
PromptArgumentError up front so you find out immediately that the args
would be ignored.
File logging and run naming
By default run() narrates to the terminal via TerminalDisplay. For an
unattended run, redirect progress and agent output to a log file by passing
log_file= — run() constructs a FileDisplay that writes line-buffered, so
tail -f shows live progress while the run is in flight:
result = await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt="Refactor X. Emit DONE when finished.",
name="nightly-refactor", # appears in status lines + log header
log_file="/tmp/pysolated-nightly.log",
)
print(result.log_file_path) # /tmp/pysolated-nightly.log
log_file=anddisplay=are mutually exclusive — pass one or the other.RunResult.log_file_pathis the resolved path of the log file whenlog_file=was used;Noneotherwise.name=is optional. When set it prefixes every status line in bothTerminalDisplayandFileDisplay([nightly-refactor] Iteration 1/3) and appears in the log file's first line so concurrent runs are distinguishable at a glance.- Both displays satisfy the same
Displayprotocol — the orchestrator is unchanged; it just receives a different impl.
Abort / cancellation
A run can be cancelled mid-flight with an asyncio.Event passed as signal=.
Setting the event aborts the in-flight iteration: the sandbox.exec is
cancelled (on no_sandbox that kills the host subprocess) and run() raises
asyncio.CancelledError promptly instead of waiting for the agent to finish.
import asyncio
from pysolated import run, claude_code, no_sandbox
async def main():
abort = asyncio.Event()
async def fire_abort_after(seconds: float) -> None:
await asyncio.sleep(seconds)
abort.set()
asyncio.create_task(fire_abort_after(5.0))
try:
await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt="long task",
signal=abort,
)
except asyncio.CancelledError:
print("aborted")
asyncio.run(main())
- Setting the event between iterations stops the outer loop before the next iteration starts.
- Pre-setting the event before
run()is called aborts immediately, without invoking the agent. - From the CLI, Ctrl-C maps onto the same abort signal: the SIGINT handler
sets the event so the orchestrator cancels cleanly and the CLI exits with
status
130instead of tearing through asyncio with aKeyboardInterrupt.
Agent failure surfacing
When the agent subprocess exits non-zero, run() raises AgentExecutionError
— a structured exception from the library's hierarchy, carrying the exit code
and the tail of stderr/stdout most likely to explain the crash:
from pysolated import AgentExecutionError, run, claude_code, no_sandbox
try:
await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt="...",
)
except AgentExecutionError as exc:
print(exc.exit_code) # e.g. 127
print(exc.stderr_tail) # last lines of stderr
print(exc.stdout_tail) # last lines of stream-json output
exit_codeis the subprocess exit status.stderr_tail/stdout_tailare truncated to the last ~50 lines so the exception stays readable even when the agent emitted megabytes of stream-json before crashing.str(exc)includes the exit code and the relevant tail — what the CLI echoes to the user (exit1).- Distinct from
IdleTimeoutError(a stuck agent) andStructuredOutputError(a malformed payload); each names its own failure mode so callers can branch on the cause.
Structured output
A structured output is a schema-validated payload the agent emits inside
a named XML tag in its own prose. Ask for one via output= on run():
from pydantic import BaseModel
from pysolated import Output, run, claude_code, no_sandbox
class Answer(BaseModel):
answer: int
rationale: str
result = await run(
agent=claude_code("claude-opus-4-7"),
sandbox=no_sandbox(),
prompt=(
"Compute the answer. "
"Reply with <result>{\"answer\": int, \"rationale\": str}</result>."
),
output=Output.object("result", Answer),
)
print(result.output.answer) # typed: int
print(result.output.rationale) # typed: str
Two flavours, both consumed via output=:
Output.object(tag, model)— JSON-parses the inner text and validates it against the Pydanticmodel. A```json … ```(or bare``` … ```) fence inside the tag is unwrapped automatically.Output.string(tag)— returns the raw inner text, whitespace-trimmed; no JSON parsing, no schema.
When the agent emits the tag multiple times — common during self-correction — the last occurrence wins.
Structured output is orthogonal to the completion signal; a run may use either, both, or neither. Two up-front guards run before any agent work, so a misconfigured call fails fast:
max_iterations != 1is rejected (the payload must unambiguously belong to the iteration that produced it).- The resolved prompt must contain the configured opening tag — a missing instruction is caught before paying for the run.
On a tag that does parse and validate, RunResult.output holds the model
instance (object mode) or the trimmed string (string mode). On failure (tag
missing, JSON parse error, schema mismatch), run() raises
StructuredOutputError carrying tag, raw_matched (the inner text when
any was found), and the underlying cause (the JSONDecodeError or
pydantic.ValidationError) so you can fix the prompt or schema without
re-running the agent.
CLI
uv run pysolated run --prompt "say hi"
The CLI is a thin Typer layer over the same run() engine. Flags:
| Flag | Default | Description |
|---|---|---|
--prompt |
(see below) | Inline prompt, sent to the agent verbatim. Mutually exclusive with --prompt-file; one is required. |
--prompt-file |
(see below) | Path to a prompt template. {{KEY}} placeholders are substituted from --prompt-arg overlaid on built-ins (branch); !`cmd` expressions are run through the sandbox and replaced by stdout. |
--prompt-arg |
(none) | KEY=VALUE argument for --prompt-file. Repeatable. Rejected when combined with --prompt. |
--agent |
claude-code |
Agent provider to drive. Registered names: claude-code, codex. |
--model |
claude-opus-4-7 (claude-code only) |
Model id for the chosen agent. Required for --agent codex (no default). |
--effort |
(none) | Reasoning effort (low|medium|high|xhigh). Accepted by codex; rejected by claude-code. |
--cwd |
current dir | Repo directory to anchor the run to. |
--permission-mode |
(none) | Claude --permission-mode; mutually exclusive with skip-permissions. Rejected by --agent codex. |
--name |
(none) | Optional name for the run. Appears as a [name] prefix on every status line and in the log file header. |
--log-file |
(none) | Write progress and agent output to this path instead of the terminal. tail -f shows live progress; RunResult.log_file_path reports the path. |
--max-iterations |
1 |
Maximum agent invocations in the loop. |
--completion-signal |
<promise>COMPLETE</promise> |
Substring in the agent's own output that ends the loop early (tool inputs/outputs it reads are never matched). Repeat the flag to match any of several. |
--idle-timeout |
600 |
Seconds without output before failing with an idle error. |
--completion-timeout |
60 |
Grace seconds after the completion signal before forcing success. |
Examples:
# multi-iteration run that stops as soon as the agent emits a custom signal
uv run pysolated run \
--prompt "Refactor X. Emit DONE when finished." \
--max-iterations 5 \
--completion-signal DONE
# match either of two completion signals, against another repo
uv run pysolated run --prompt "..." --cwd /path/to/repo \
--completion-signal DONE --completion-signal FINISHED
# shorten the idle timeout and the post-signal grace window
uv run pysolated run --prompt "..." --idle-timeout 120 --completion-timeout 30
# unattended run: write progress + agent output to a log file, name the run
uv run pysolated run --prompt "..." \
--name nightly-refactor --log-file /tmp/pysolated-nightly.log
# resolve a prompt template, supplying one user argument; `branch` is built-in
uv run pysolated run \
--prompt-file prompts/refactor.txt \
--prompt-arg area=auth \
--completion-signal DONE
# drive Codex instead of Claude; --model is required, --effort is optional
uv run pysolated run --agent codex --model gpt-5-codex --effort high \
--prompt "say hi"
On completion the CLI prints the iteration count, the matched completion signal, the commits the agent made, and token usage.
Project scaffolding (init)
pysolated init scaffolds the .pysolated/ config directory for an
agent × sandbox combo (see Getting started). It's a
tri-state wizard per choice: a flag wins when given; an omitted flag prompts
interactively in a terminal; an omitted flag with no TTY exits 2 naming the
missing flag, so all-flags invocations stay fully headless and scriptable.
| Flag | Default | Description |
|---|---|---|
--agent |
(prompted) | Agent to wire into the driver: claude-code or codex. Prompted when omitted in a terminal; required in non-interactive mode. |
--sandbox |
(prompted) | Sandbox to wire into the driver: podman or docker. Prompted when omitted in a terminal; required in non-interactive mode. |
--model |
the agent's default | Model id baked into the driver. Defaults to claude-opus-4-7 for claude-code and gpt-5-codex for codex. |
--cwd |
current dir | Repo directory the .pysolated/ config directory is created in. |
init exits 2 if a passed --agent/--sandbox is unregistered, and exits
1 if .pysolated/ already exists (it never overwrites).
# interactive wizard — prompts for whatever you don't pass
uv run pysolated init
# headless: both choices as flags (no prompts, scriptable)
uv run pysolated init --agent codex --sandbox docker
# override the model baked into the driver
uv run pysolated init --agent claude-code --sandbox podman --model claude-opus-4-8
Podman image lifecycle
The pysolated podman subgroup manages the image the podman(...) sandbox
runs. Both subcommands default to the same derived tag the provider uses —
pysolated:<sanitized-host-dirname> — so a fresh repo goes from zero to a
running agent without leaving pysolated.
# build ./Containerfile to the derived tag
uv run pysolated podman build-image
# build a different Containerfile to an explicit tag
uv run pysolated podman build-image --file docker/Containerfile --image my-agent:latest
# remove the derived (or a named) image
uv run pysolated podman remove-image
uv run pysolated podman remove-image --image my-agent:latest
pysolated podman build-image — runs podman build -f <file> -t <tag> <cwd>:
| Flag | Default | Description |
|---|---|---|
--file / -f |
Containerfile |
Containerfile path passed to podman build -f. |
--image |
pysolated:<sanitized-host-dirname> |
Image tag to build. |
pysolated podman remove-image — the matching podman rmi:
| Flag | Default | Description |
|---|---|---|
--image |
pysolated:<sanitized-host-dirname> |
Image tag to remove. |
Both exit non-zero (propagating Podman's exit code) and echo Podman's stderr when the underlying command fails.
Docker image lifecycle
The pysolated docker subgroup manages the image the docker(...) sandbox
runs. Like Podman, both subcommands default to the derived tag the provider
uses — pysolated:<sanitized-host-dirname> — but Docker builds default to
Dockerfile and auto-inject host UID/GID build args for ownership alignment.
# build ./Dockerfile to the derived tag
uv run pysolated docker build-image
# build a different Dockerfile to an explicit tag
uv run pysolated docker build-image --file docker/Dockerfile --image my-agent:latest
# remove the derived (or a named) image
uv run pysolated docker remove-image
uv run pysolated docker remove-image --image my-agent:latest
pysolated docker build-image — runs docker build -f <file> -t <tag> <cwd>,
with AGENT_UID and AGENT_GID build args auto-injected from the host:
| Flag | Default | Description |
|---|---|---|
--file / -f |
Dockerfile |
Dockerfile path passed to docker build -f. |
--image |
pysolated:<sanitized-host-dirname> |
Image tag to build. |
--build-arg |
host AGENT_UID / AGENT_GID |
Extra docker build --build-arg KEY=VALUE. Repeatable; explicit AGENT_UID or AGENT_GID values override the auto-injected defaults. |
pysolated docker remove-image — the matching docker rmi:
| Flag | Default | Description |
|---|---|---|
--image |
pysolated:<sanitized-host-dirname> |
Image tag to remove. |
Both exit non-zero (propagating Docker's exit code) and echo Docker's stderr when the underlying command fails.
Architecture
Three injectable Protocol seams (per
ADR 0002), built up front so Docker and
other agents are additive:
AgentProvider— builds the agent command (an argv list + optional stdin, per ADR 0001) and parses its stream output.SandboxProvider— runs a command, streaming stdout line-by-line.Display— narrates the run (the orchestrator's test-substitution point). Two impls ship:TerminalDisplay(default) andFileDisplay(selected bylog_file=/--log-file).
Development
uv sync
uv run pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pysolated-0.1.0.tar.gz.
File metadata
- Download URL: pysolated-0.1.0.tar.gz
- Upload date:
- Size: 216.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bc3d76b9e0bb2137274b1818772256b1a55f032ef4c819e6064a168d57aa86f
|
|
| MD5 |
33025f110139634658587fadc3dca730
|
|
| BLAKE2b-256 |
ca74722d147b754ed75af26146759579c7449f0264a36fa26c2ffc378ed0f001
|
File details
Details for the file pysolated-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pysolated-0.1.0-py3-none-any.whl
- Upload date:
- Size: 76.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48828146cd66b8efd7e50284ec857192a550a28166bd716c725e4170d43efa73
|
|
| MD5 |
b96c4ecbceb1b4ebbd6e918cc132922c
|
|
| BLAKE2b-256 |
5e84f5825a6053978f313e85755f5345061a86af47ba8f09d331a49fa89edda8
|