feat: capture worker artifacts before teardown and carry native limits
All checks were successful
ci / validate (push) Successful in 2m39s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 12:36:15 +02:00
parent 44991600f3
commit fb57c0228f
9 changed files with 159 additions and 3 deletions

View file

@ -553,3 +553,47 @@ def test_reachability_preparation_failure_is_reported_and_destroyed():
assert 'private owner detail' not in str(post.call_args)
assert rein.calls == []
manager.destroy.assert_called_once_with('sbx1')
def test_artifact_capture_occurs_after_cleanup_before_destroy():
manager = MagicMock()
manager.create.return_value = _fake_status()
rein = _FakeRein()
order = []
rein.cleanup_session = lambda session: order.append("cleanup")
manager.destroy.side_effect = lambda sandbox: order.append("destroy")
def capture(sandbox, summary):
assert summary.commit_sha == "deadbeef"
assert sandbox._owner_execute is not None
order.append("capture")
result = run_execution(_request(), catalog=_catalog_with_readiness(), rein=rein,
manager=manager, artifact_capture=capture)
assert result.ok
assert order == ["cleanup", "capture", "destroy"]
assert "bundle" not in result.model_dump_json()
def test_artifact_capture_failure_still_destroys_sandbox():
manager = MagicMock()
manager.create.return_value = _fake_status()
def capture(*args):
raise ValueError("invalid artifact")
result = run_execution(_request(), catalog=_catalog_with_readiness(), rein=_FakeRein(),
manager=manager, artifact_capture=capture)
assert not result.ok
assert result.evidence.failure_stage == "artifact_capture"
assert result.evidence.sandbox_destroy == "succeeded"
manager.destroy.assert_called_once_with("sbx1")
def test_cleanup_failure_never_captures_artifact():
manager = MagicMock()
manager.create.return_value = _fake_status()
rein = _FakeRein()
rein.cleanup_session = MagicMock(side_effect=ValueError("cleanup"))
capture = MagicMock()
result = run_execution(_request(), catalog=_catalog_with_readiness(), rein=rein,
manager=manager, artifact_capture=capture)
assert not result.ok
capture.assert_not_called()
manager.destroy.assert_called_once()

View file

@ -210,3 +210,19 @@ def test_cleanup_session_removes_only_generated_task_file() -> None:
{"transport": transport, "task_file": "/sandbox/task.json", "generated_task_file": False}
)
transport.remove_file.assert_not_called()
def test_profile_native_limits_are_carried_in_owned_task_file():
from glas_harness.contract import ExecutionLimits
profile = ProfileCatalog().resolve("harness.agent-dev-local@1.0.0")[0].model_copy(update={
"limits":ExecutionLimits(max_budget_usd=.25,max_turns=3,timeout_seconds=30)})
transport=MagicMock(spec=ExecutionTransport)
transport.workspace="/sandbox"
transport.git_head.return_value="a"*40
rein=ReinAharness()
with patch("glas_harness.reins.rein_aharness.transport_from_sandbox",return_value=transport):
rein.start_session(profile,{"title":"task","description":"prompt asks for unlimited money"},SandboxHandle(sandbox_id="fixture",host="localhost"))
assert transport.write_task_file.call_args.args[0]["max_budget_usd"] == .25
assert transport.write_task_file.call_args.args[0]["max_turns"] == 3
with pytest.raises(ValueError,match="Glas-generated"):
rein.start_session(profile,{"task_file":"/untrusted.json"},object())