M4.1: toolchain preflight
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m59s
CI / e2e (pull_request) Successful in 10m33s

A model works out and sets up what a project needs on its build VM;
code makes sure that happens, bounds it and checks the result (D3).

- Before each driver run on a project, one round trip checks the build
  VM: the harness's own baseline (missing: the item waits, a template
  problem), the workspace clone (missing: cloned again and said; the
  item resumes from what it pushed) and a per-project marker.
- No marker (a new project, a new or wiped VM): the item's driver model
  runs a bounded preflight first, with its own prompt, the execution
  tools (screened as ever) and toolchain_ready. The harness runs each
  reported version command itself (version-shaped commands only, even
  from the marker, which the model's commands could edit) and writes
  the marker. Gaps and an unfinished preflight are facts and work goes
  on; the thread hears only of installs and gaps. A rebuilt VM starts
  from the last toolchain.
- Later runs check the versions again; a change is a fact and a thread
  line. The PR body says what the work was tested with.
- A changed host key is its own failure (HostKeyChanged): the item
  waits for the operator's `trust new host key`, which drops the pin;
  then the VM is set up again.
- A prompt line: install what the work needs on the build VM, say why,
  and put lasting needs in the project's own pins.
- tuning.preflight: max_turns, command_timeout_s.
- The property walk gains a VM wipe and ready_before_work (checked
  against a mutant that skips preflight); parked_work_safe accepts that
  a wipe loses what wasn't pushed (D3).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-08 15:43:30 +00:00
1 parent 4564cd6f3f
commit 9968a0922a
19 files changed
+1103 -10

No files matched your search

+17
View File
@@ -35,6 +35,7 @@ from nisse_dev.canonical import ActionVerdict
from nisse_dev.config import AgentConfig, LoopTuning
from nisse_dev.execution import SSHHost
from nisse_dev.gitea import GiteaClient, GiteaError
from nisse_dev.loop import preflight
from nisse_dev.loop.build import RunningLoop, build_loop_agent
from nisse_dev.loop.items import (
ANSWER,
@@ -55,6 +56,9 @@ MAX_ANSWERS = 5 # per wait for something else, before giving up
BUG_GRACE_S = 20
_PREFLIGHTED: list[str] = []
@pytest.fixture
def running(
live_config: AgentConfig,
@@ -99,6 +103,17 @@ def running(
# What the agent reads when told a new project is ready: replaced by
# a test that adds one (D8).
source = [config]
if not _PREFLIGHTED:
# The test hosts keep their markers: the session's first test
# gets a real preflight (M4.1), the rest check versions.
_PREFLIGHTED.append(project.name)
SSHHost(
project.build_host.address,
key=project.build_host.key,
user=project.build_host.user,
port=project.build_host.port,
state_dir=tmp_path / "ssh-marker",
).execute(f'rm -f "{preflight.marker_path(project.name)}"')
agent = build_loop_agent(config, load=lambda: source[0])
agent.source = source # type: ignore[attr-defined]
# The test drives intake and PR watching itself, not on a timer.
@@ -318,6 +333,8 @@ def test_the_slice_end_to_end(
assert marker in diff, "the pushed fix doesn't do what the comment asked"
# --- the operator approves and merges; the agent closes the item ---
body = gitea._request("GET", f"/repos/{repo}/pulls/{pr}")["body"]
assert "Tested with: " in body, body # M4.1: the toolchain checked
operator.merge_and_close(issue)
# --- M3.1: the fast path judged every call; none needed an allow ---