Treadle Headless

A standalone Python runtime for workflows that invoke headless workers. Workflow code owns branches, checks and correction loops. The runtime owns subprocess launches, separate output paths, bounded execution and run records.

This package is an independent implementation, staged in a Commonplace workshop. It does not import Commonplace or require a Git checkout. It is not a replacement for an existing Treadle installation. Initial platform: Linux, Python 3.11 or newer.

A call

import sys
from pathlib import Path

from treadle_headless import CommandWorker, Request, Run

worker = CommandWorker(argv=(
    sys.executable, str(Path("worker.py").resolve()), "{prompt}",
))
request = Request(
    call_id="summarize-v1",
    task="Read the supplied source and write the requested summary file.",
    inputs={"source.txt": "Material to summarize."},
    required_outputs=("summary.txt",),
    worker=worker,
    timeout_seconds=60,
)

with Run(Path("run-records"), Path.cwd()) as run:
    result = run.call(request)
    if result.status == "delivered":
        print(result.outputs["summary.txt"])
    else:
        print(result.status)

worker.py represents your worker executable. It receives the path of a generated prompt that lists absolute input and output paths. It must read the inputs and write the named files. Standard output is execution evidence, not the requested file result. Command arguments are passed without a shell.

The generic command adapter also accepts {input_dir}, {output_dir}, {problem_file}, {launch_dir} and {workdir} path tokens. Named inputs and outputs are single filenames. Each physical launch has fresh paths, including when a call retries.

Resume and corrections

Reopen the same run directory and repeat the same call to reuse a committed result. Both the stable call identity and declared request must match. Different identities request independent work even when their prompts match.

The caller must keep the surrounding environment compatible with reuse. Changing ambient files, tools or workflow logic may require a new run. The runtime does not infer those dependencies, pin code or manage environments.

Check delivered content in your workflow. For a correction, create a new call with a stable identity for that correction, previous content and findings as inputs. Bound that loop in workflow code. Replay a cached result through its check; delivery does not certify acceptance. Use fixed sibling snapshots for checks whose answer depends on other parallel results.

The default is one physical launch. Larger limits and execution retry policy must be explicit. Each launch reservation spends an allowance before process start. Restarting does not refund consumed allowances. An unresolved launch reservation blocks automatic work: establish that the old worker has stopped before acknowledging it with acknowledge_stopped. This operation records your assertion; it cannot prove that the old process is gone.

run.parallel(requests, max_workers=...) preserves result order and waits for all submitted work to settle before reporting an exception. It does not create a dependency scheduler. The limit applies to that group; callers creating other threads or parallel groups remain responsible for their combined concurrency.

Harness adapters

from treadle_headless.adapters import codex_worker, pi_worker

codex = codex_worker("gpt-6.1-sol", sandbox="workspace-write", effort="low")
pi = pi_worker(
    "gpt-6.1-sol", provider="openai", tools=("read", "write"),
    project_trust="no-approve",
)

Pass one of these as the request's worker. Put the run records inside a directory the worker can write; the adapter does not widen the sandbox for record paths. Pi's project-trust option controls resource loading, not OS permissions. A claude_worker command factory is also available, but has not been exercised with a real model in this implementation session.

Codex and Pi have each passed a bounded real file-copy probe and reuse check. Their raw event streams remain in each launch's stdout.log. The standalone parse_evidence(harness, stdout_text) helper extracts available session and model fields; it does not infer the served model from the requested model. Attempt.evidence is a separate optional worker-file protocol, not an automatic interpretation of those CLI streams.

Outcomes and recovery

CallResult.status distinguishes delivered, incomplete, worker_problem, usage_limit, process_error, timeout, launch_error, protocol_error, and an explicitly acknowledged interrupted launch. RetryPolicy(on=(...), backoff_seconds=...) selects which eligible execution outcomes to retry up to Request.max_launches. Usage-limit results are not automatically retried. Recognition currently comes from the structured problem-file protocol; provider quota messages in raw harness logs are not universally classified.

An uncertain launch raises UncertainLaunchError before new work starts. Inspect run.unresolved_launches(), establish that the old worker and its children have stopped, then call run.acknowledge_stopped(launch_id). To allow a subsequent launch, the original request must have remaining allowance and an explicit retry policy including interrupted. Otherwise start a new run deliberately. Changing execution policy or limits changes declared request identity; it is not a way to continue the original call's budget.

Corrupt records raise RecordError. A persistence failure can poison the current controller: close and reopen it before inspection or recovery. Never delete a reservation merely to suppress uncertainty.

Boundaries

Separate output directories prevent accidental reuse of another launch's files. They do not restrict a worker's filesystem or network access. The caller configures permissions, credentials and the harness environment. External effects are not transactional and may need caller-owned recovery.

See the execution contract for guarantees and limits, and harness evidence for adapter evidence and tested versions. Support claims must distinguish deterministic tests from actual model invocations.

Development

Run commands from this package directory:

uv sync
uv run pytest
uv run ruff check .
uv build

Tests use real subprocesses with deterministic worker fixtures. They do not require model credentials. Real-harness probes are separate, bounded operations. No package has been published by creating this source tree.

The ordinary-file example demonstrates checks, corrections and resume. To run a real, credential-using probe deliberately:

uv run python examples/smoke_harness.py codex /tmp/my-codex-probe --model gpt-6.1-sol
uv run python examples/smoke_harness.py pi /tmp/my-pi-probe --model gpt-6.1-sol

Complete file listing (generated at build time)