goal 异常退出记 interrupted,可断点续跑
长 goal 实弹(2026-10-28):宿主机内存挤兑致回合挂起 59 分钟,恢复时 goal 进程异常退出、无结论行,云端旧语义(returncode 非 0 一律 failed)把可续跑的断点判成终局。 修法:host.py run_goal_loop 回合异常统一记 interrupted 并打印 goal interrupted after rounds 标记行;cloud/sandbox.py 四处 goal 解析同步新语义(标记或无结论非 0 退出 -> interrupted,有结论的非 0 退出仍 failed);worker 终局审计名与状态对齐。单测:sandbox 新测 5 个、host 新测 1 个、更新旧测 1 个;三套件 cloud 1017、host 61、deploy 29 全绿。只本地提交,未推未部署。
This commit is contained in:
1 parent
d129f7bf8c
commit
94137c4a29
6 files changed
+287
-29
No files matched your search
@@ -309,7 +309,10 @@ class TestGoalExecutors(unittest.TestCase):
|
||||
self.assertIsNone(result["goal_met"])
|
||||
self.assertIn("无结论", result["goal_error"])
|
||||
|
||||
def test_local_goal_task_nonzero_is_failed(self):
|
||||
def test_local_goal_task_nonzero_without_conclusion_is_interrupted(self):
|
||||
# 2026-10-28 长 goal 实弹:无结论的异常退出(进程被杀、
|
||||
# 连接断后恢复时抛错)必须记 interrupted 保住断点续跑,
|
||||
# 绝不能一律 failed(旧语义直接断了 60 分钟工作的活路)。
|
||||
def fake(cmd):
|
||||
return SimpleNamespace(returncode=1, stdout="", stderr="boom")
|
||||
|
||||
@@ -317,9 +320,77 @@ class TestGoalExecutors(unittest.TestCase):
|
||||
runner=fake).goal_task(
|
||||
{"id": "t1", "prompt": "do it",
|
||||
"verify_command": "true"})
|
||||
self.assertEqual(result["status"], "failed")
|
||||
self.assertEqual(result["status"], "interrupted")
|
||||
self.assertIsNone(result["goal_met"])
|
||||
self.assertIn("boom", result["goal_error"])
|
||||
|
||||
def test_local_goal_task_nonzero_with_unmet_stays_failed(self):
|
||||
def fake(cmd):
|
||||
return SimpleNamespace(
|
||||
returncode=1,
|
||||
stdout="round 1: turn=completed verify_rc=1 "
|
||||
"verify_out=file missing\n"
|
||||
"goal unmet after rounds\n",
|
||||
stderr="")
|
||||
|
||||
result = LocalExecutor("/fake/host.py",
|
||||
runner=fake).goal_task(
|
||||
{"id": "t1", "prompt": "do it",
|
||||
"verify_command": "true"})
|
||||
self.assertEqual(result["status"], "failed")
|
||||
self.assertIs(result["goal_met"], False)
|
||||
|
||||
def test_local_goal_task_interrupted_marker(self):
|
||||
def fake(cmd):
|
||||
return SimpleNamespace(
|
||||
returncode=0,
|
||||
stdout="round 1: turn=interrupted verify_rc=- "
|
||||
"verify_out=\n"
|
||||
"goal interrupted after rounds: relay boom\n",
|
||||
stderr="")
|
||||
|
||||
result = LocalExecutor("/fake/host.py",
|
||||
runner=fake).goal_task(
|
||||
{"id": "t1", "prompt": "do it",
|
||||
"verify_command": "true"})
|
||||
self.assertEqual(result["status"], "interrupted")
|
||||
self.assertIn("relay boom", result["goal_error"])
|
||||
|
||||
def test_docker_goal_task_nonzero_without_conclusion_is_interrupted(self):
|
||||
def fake_runner(cmd, timeout=None):
|
||||
return SimpleNamespace(returncode=1, stdout="",
|
||||
stderr="boom")
|
||||
|
||||
with mock.patch("sandbox.shutil.which",
|
||||
return_value="/usr/bin/docker"):
|
||||
executor = DockerExecutor(
|
||||
"/fake/host.py", runner=fake_runner,
|
||||
env={"XCM_API_KEY": "placeholder-not-a-real-key"})
|
||||
result = executor.goal_task(
|
||||
{"id": "t1", "prompt": "do it",
|
||||
"verify_command": "test -f x"})
|
||||
self.assertEqual(result["status"], "interrupted")
|
||||
self.assertIn("boom", result["goal_error"])
|
||||
|
||||
def test_docker_goal_resume_nonzero_without_conclusion_is_interrupted(self):
|
||||
# 覆盖 docker 侧两处 resume 解析(goal_resume_task 与
|
||||
# goal_resume_breakpoint_task 共用 shape B 链路,两个都测)。
|
||||
def fake_runner(cmd, timeout=None):
|
||||
return SimpleNamespace(returncode=1, stdout="",
|
||||
stderr="boom")
|
||||
|
||||
with mock.patch("sandbox.shutil.which",
|
||||
return_value="/usr/bin/docker"):
|
||||
executor = DockerExecutor(
|
||||
"/fake/host.py", runner=fake_runner,
|
||||
env={"XCM_API_KEY": "placeholder-not-a-real-key"})
|
||||
for method in ("goal_resume_task",
|
||||
"goal_resume_breakpoint_task"):
|
||||
result = getattr(executor, method)(
|
||||
{"id": "t1", "prompt": "do it"})
|
||||
self.assertEqual(result["status"], "interrupted")
|
||||
self.assertIn("boom", result["goal_error"])
|
||||
|
||||
def test_local_goal_task_already_exists_names_retry(self):
|
||||
def fake(cmd):
|
||||
return SimpleNamespace(
|
||||
|
||||
Reference in new issue
Block a user