Skip to content

Local runs

flightdeck experiment run <suite> --backend local --data <dir> runs every trial of the suite now, one after another, in Docker on this machine. It writes them into the data plane at <dir> with source = local.

It is for fixtures and CI; this repository's own CI runs tasks/fixtures/local.yaml on every PR. It is never for team tasks: a task with sealed = true or mined_from set is refused. Team tasks and every model trial run on hangar, where credentials and egress policy live.

There is no cost gate: the only driver makes no model calls and spends nothing. status and collect recognize a local run set by its local- prefix and read its evidence from <dir>/raw/<run-set>/.

The isolation is ai-dlc's evaluation runner, ported at its handoff commit f5be4f4 (src/flightdeck/ported/aidlc/, each file listing what changed). The flightdeck side is src/flightdeck/runners/local.py.

Environment

The task's environment/Dockerfile is built to an image id. Or [environment] docker_image, which must be pinned by digest, is used as is and never pulled. The project is the image's working directory ([environment] workdir, else the image's WORKDIR). The image needs git.

Attempt

A controller-owned stager copies the working directory into a per-attempt volume, dropping any .git. The controller commits it as a fixed anchor (refs/flightdeck/anchor, neutral author, fixed date).

The agent's container runs as uid 1000 with --read-only --cap-drop=ALL --security-opt=no-new-privileges, memory and process limits, tmpfs /tmp and home, and --network=none. Nothing from the host is mounted.

After the agent stops, a separate read-only collector copies the volume out. Then every container and volume is removed; cleanup-ledger.jsonl names any that weren't.

Grading

The task's tests/ stay on the controller. The grader command ([flightdeck.grader] command) runs on the collected tree in a fresh container with no network and the same isolation. It gets FLIGHTDECK_WORKDIR and FLIGHTDECK_REWARD_DIR (Tasks). Its reward.json is kept beside the trial's evidence and in grades.data; its exit code is grades.exit_code.

Driver

A variant's harness must be deterministic; any other harness is refused before anything runs.

deterministic makes no model calls. It runs the task's solution/deterministic.json (ai-dlc's script format: {"steps": [argv, or {"write": path, "from": file}]}, one command per step), else Harbor's oracle (copy solution/ in, run solve.sh).

The port's claude-code@<version> driver was removed in KAN-93. It was never qualified against a live model, and model trials belong on hangar (ADR 0008).

Rows

A local run set reads like a hangar experiment to stats compare, reports and specs (experiment_id = the run set id):

Table From
runs one per trial: outcome succeeded (the attempt completed), failed (outcome_class = infra_error when provisioning, staging or a resource limit failed; else timeout, cancellation or damaged evidence, in reason); diff_files from the Git observer; truncated when the attempt or the observation is incomplete, so specs answer unknown rather than pass
grades the grader's exit code, seconds, reward.json and the tests' files_digest
spans one tool.call per deterministic step (tool_kind shell, or edit for a file write). Each has hangar's tool_kind and shell_ops (ported from hangar's traces.py) and seq in order
events one git.commit per post-anchor commit the Git observer confirms, placed after the step that made it
test_cases one trusted test.run per deterministic step whose command is a test (total from unittest's or pytest's summary)

Trust follows hangar's rule. A test.run is trusted because the controller ran the command and read its result, which the agent can't fake.

So tests-before-commit evaluates on local runs. On tasks/fixtures/local.yaml, the csv-validator script tests before committing (pass) and semver's solve.sh commits without testing (fail). A step that both tests and commits counts the test first, unless its command commits first.