|
2 | 2 | """Unpaid safety/control-flow regressions; all agent and remote calls are mocked.""" |
3 | 3 | import json |
4 | 4 | import os |
| 5 | +from contextlib import ExitStack |
5 | 6 | from pathlib import Path |
6 | 7 | import shutil |
7 | 8 | import subprocess |
| 9 | +import sys |
8 | 10 | import tempfile |
9 | 11 | import unittest |
10 | 12 | from unittest.mock import patch |
11 | 13 |
|
12 | 14 | import common as C |
| 15 | +import ablation as A |
13 | 16 | import eval as E |
14 | 17 | import loop as L |
15 | 18 | import loop_corrections as LC |
16 | 19 | import rescore |
| 20 | +import report as R |
17 | 21 | import teammate as T |
| 22 | +from budget.review_selftest import RunnerBudgetReviewTests |
18 | 23 |
|
19 | 24 |
|
20 | 25 | class HarnessTests(unittest.TestCase): |
@@ -122,6 +127,66 @@ def test_reviewer_completion_marker(self): |
122 | 127 | self.assertEqual(T.clean_message("Fix the timestamps.\nDONE"), "Fix the timestamps.") |
123 | 128 | self.assertTrue(C.completion_prompt("review").endswith("standalone DONE line.")) |
124 | 129 |
|
| 130 | + def test_errored_reviewer_text_is_not_forwarded(self): |
| 131 | + task = {"id": "train-test", "split": "train"} |
| 132 | + first = {"session_id": "s1", "completed": True, "workdir": "/unused/work"} |
| 133 | + review = {"text": "Partial correction", "lgtm": False, "cost": 0, "error": "reviewer timed out"} |
| 134 | + with tempfile.TemporaryDirectory() as d, patch.object(C, "run_task", return_value=first), \ |
| 135 | + patch.object(T, "review", return_value=review), patch.object(LC, "followup") as followup, \ |
| 136 | + patch.object(LC, "captured_signals", return_value={}): |
| 137 | + result = LC.train_session(d, task, None, "mock", "mock", 1, os.path.join(d, "eval.jsonl")) |
| 138 | + self.assertEqual(result["error"], "reviewer timed out") |
| 139 | + followup.assert_not_called() |
| 140 | + |
| 141 | + def test_system_prompt_trace_detects_loaded_playbook(self): |
| 142 | + # session/prompt.ts emits this content via Tracer.logSpan; FileExporter serializes it as JSON. |
| 143 | + trace = {"spans": [{"name": "system-prompt", "output": {"parts": 1, |
| 144 | + "content": '<auto_loaded_skill name="team-playbook">\nUse cents → dollars.\n</auto_loaded_skill>'}}]} |
| 145 | + self.assertTrue(C.playbook_in_trace(json.dumps(trace, ensure_ascii=False))) |
| 146 | + self.assertFalse(C.playbook_in_trace(json.dumps(trace).replace("team-playbook", "other-skill"))) |
| 147 | + |
| 148 | + def test_native_signal_reflection_reopens_only_its_session(self): |
| 149 | + with tempfile.TemporaryDirectory() as d: |
| 150 | + work, maint = os.path.join(d, "work"), os.path.join(d, "maint") |
| 151 | + path = Path(LC.signals_file(work)) |
| 152 | + path.parent.mkdir(parents=True) |
| 153 | + rows = [{"id": "one", "session": "s1", "status": "consumed", "consumedBy": "auto"}, |
| 154 | + {"id": "two", "session": "s2", "status": "consumed", "consumedBy": "auto"}] |
| 155 | + path.write_text("".join(json.dumps(row) + "\n" for row in rows)) |
| 156 | + def reflect(run_dir, cwd, args): |
| 157 | + self.assertEqual(args[-2:], ["--signals-from", work]) |
| 158 | + signals = C.read_jsonl(str(path)) |
| 159 | + self.assertEqual(signals[0]["status"], "open") |
| 160 | + self.assertNotIn("consumedBy", signals[0]) |
| 161 | + self.assertEqual(signals[1], rows[1]) |
| 162 | + return subprocess.CompletedProcess([], 0, '{}', '') |
| 163 | + with patch.object(LC, "learn", side_effect=reflect): |
| 164 | + result = LC.reflect_session(d, maint, {"workdir": work, "session_id": "s1"}, |
| 165 | + "mock", {"signals_from": True}) |
| 166 | + self.assertTrue(result["ok"]) |
| 167 | + |
| 168 | + def test_ablation_rejects_failed_reflection(self): |
| 169 | + with tempfile.TemporaryDirectory() as d, ExitStack() as stack: |
| 170 | + stack.enter_context(patch.object(sys, "argv", ["ablation.py", "--run-dir", d])) |
| 171 | + for name in ("require_learn", "require_dbt", "setup_users", "warm_users"): |
| 172 | + stack.enter_context(patch.object(C, name)) |
| 173 | + stack.enter_context(patch.object(C, "resolve_models", return_value=("mock", "mock"))) |
| 174 | + stack.enter_context(patch.object(C, "select_tasks", return_value=[{"id": "train-test"}])) |
| 175 | + stack.enter_context(patch.object(C, "run_many", return_value=[{"task": "train-test"}])) |
| 176 | + stack.enter_context(patch.object(L, "maintainer", return_value=d)) |
| 177 | + stack.enter_context(patch.object(L, "reflect", return_value={"ok": False, "error": "failed"})) |
| 178 | + with self.assertRaisesRegex(SystemExit, "reflection failed"): |
| 179 | + A.main() |
| 180 | + self.assertFalse((Path(d) / "playbook-nofeedback.md").exists()) |
| 181 | + |
| 182 | + def test_report_counts_each_error_source_once(self): |
| 183 | + base = {"arm": "none", "split": "heldout", "task": "test"} |
| 184 | + records = [base, dict(base, error="setup failed"), dict(base, errors=["agent error"]), |
| 185 | + dict(base, verify={"error": "unparsable"}), |
| 186 | + dict(base, error="failed", errors=["event"], verify={"error": "unparsable"})] |
| 187 | + rows, _ = R.arm_split_rows(records) |
| 188 | + self.assertEqual(rows[0]["errors"], 4) |
| 189 | + |
125 | 190 | def test_workspace_target_and_stale_hash(self): |
126 | 191 | with tempfile.TemporaryDirectory() as d: |
127 | 192 | base = Path(d) / ".altimate-code/skill/_workspace" |
|
0 commit comments