Files
Agentswarm/scripts/test-quality.py
T
Songhaoz666andClaude Opus 4.8 a289495823 复审整改(PR #24):沙箱 fail-closed 隔离门控 + 可回放 DecisionTrace
回应 Fasthei 的 Request changes 两个阻塞项:

1) 安全 / fail-closed 沙箱隔离(原仅靠 ENABLE_QUALITY_EVAL + 运维约定):
   - 新增第二道显式确认 HEICODE_SANDBOX_ISOLATED(断言运行在隔离 Pod 内)。
   - sandbox.run_tests() 与 quality.evaluate_run_quality() 执行任何代码前调用
     assert_isolated(),未确认即抛 SandboxIsolationError——不写文件、不起子进程。
   - 启动期 assert_quality_eval_safe():ENABLE_QUALITY_EVAL 开但隔离未确认 → 拒绝启动
     (平台级硬失败,非运维口头约定)。
   - 文档(security-boundary §8.1/§9、CLAUDE.md)与测试同步:test-sandbox/test-quality
     先断言未确认时硬失败,再显式确认后继续。

2) #10 DecisionTrace 可回放(原仅存被选中任务的标量):
   - Decision 现记录完整重放上下文:整个候选集(每候选 tau/eta/weight/p_norm/dependents)、
     alpha/beta/epsilon、seed、free_slots、total_weight、explore_draw、select_pick、
     select_index、explored 分支。
   - 新增 DecisionEngine.replay_decision(trace):仅凭一条 trace(无 RNG/活体状态)复现被选任务;
     test-decision-engine 断言「重放==实选」跨 50 次决策(探索+利用)成立。
   - decision-engine.md §3.3 更新为可回放 DecisionTrace。

附:新增 docs/TESTING.md(reviewer 速查:依赖安装 + 每套测试命令,复审者此前因缺 fakeredis
未能跑到断言)。本地 11 项 gate 全绿。

影响范围:Swarm(orchestrator + 测试 + 文档)。不改 Manager↔Swarm 契约;新增开关
HEICODE_SANDBOX_ISOLATED(默认未设=拒绝执行)。仍非验收:gain/Benchmark_Agent 仍 NaN。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:22:54 +08:00

128 lines
5.2 KiB
Python

"""Integration test for benchmark Group B: fixture grading -> Q_quality -> reward.
Boots the in-memory store, seeds a completed run whose tasks carry generated code, grades it
against the held-out `add_function` fixture in the sandbox, and asserts Q_quality and reward
become real. Hermetic, no model key. Run from agent_swarm_v6 (install deps first):
pip install -r orchestrator/requirements.txt -r agent/requirements.txt
python scripts/test-quality.py
(this test sets REDIS_FAKE / ENABLE_QUALITY_EVAL / HEICODE_SANDBOX_ISOLATED itself.)
"""
import asyncio
import json
import math
import os
import sys
from pathlib import Path
os.environ["REDIS_FAKE"] = "1"
os.environ["ENABLE_QUALITY_EVAL"] = "1" # gate ON for this test (executes code in the sandbox)
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from orchestrator.redis_client import redis_client
from orchestrator import swarm_runtime as sr_mod
from orchestrator.swarm_runtime import swarm_runtime
from orchestrator.task_queue import task_queue, TaskStatus
from orchestrator.quality import evaluate_run_quality, collect_generated_files
from orchestrator.sandbox import SandboxIsolationError
from benchmark.collectors.run_collector import SwarmRunMetricsCollector
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
async def _noop(self, *a, **k):
return None
# A correct implementation + the swarm's OWN test (which must NOT be the grader).
IMPL = "def add(a, b):\n return a + b\n"
RESULT = {
"success": True,
"subtasks": [
{"status": "completed", "files": [{"path": "calc.py", "action": "write", "content": IMPL}]},
{"status": "completed", "files": [{"path": "test_calc.py", "action": "write",
"content": "from calc import add\ndef test_self():\n assert add(1, 1) == 2\n"}]},
],
"usage": {"model_cost_usd": 2.0},
}
async def add_completed_task(run, task_id, result):
t = await task_queue.create_task(task_id=task_id, description=task_id,
agent_role=task_id.split("-")[-1], depends_on=[], enqueue=False)
t.status = TaskStatus.COMPLETED
t.assigned_agent_id = "A"
t.retry_count = 0
t.result = json.dumps(result)
await task_queue._save_task(t)
await swarm_runtime.attach_task(run, t.task_id)
return t
async def main():
await redis_client.connect()
sr_mod.SwarmRuntime._post_callback = _noop
body = {
"mode": "swarm",
"requirement": {"objective": "Write add(a,b)"},
"orchestration_plan": {"budget": {"max_cost_usd": 10}},
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
"metadata": {"manager_deployment_id": "m-q", "benchmark_fixture_id": "add_function"},
}
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="cq")
impl_task = await add_completed_task(run, "t-implementation", RESULT)
tasks = [impl_task]
# FAIL-CLOSED: with eval enabled but isolation NOT confirmed, grading must hard-fail (not
# silently skip and not execute code).
os.environ.pop("HEICODE_SANDBOX_ISOLATED", None)
raised = False
try:
await evaluate_run_quality(run, tasks)
except SandboxIsolationError:
raised = True
check("fail-closed: eval enabled without isolation raises", raised)
os.environ["HEICODE_SANDBOX_ISOLATED"] = "1" # confirm isolation for the rest (CI/test runner)
# file separation: impl vs the swarm's own tests
files = collect_generated_files(tasks)
check("collect splits impl vs agent tests",
[f.path for f in files["impl"]] == ["calc.py"] and len(files["agent_tests"]) == 1)
# grade against held-out fixture tests in the sandbox
quality = await evaluate_run_quality(run, tasks)
check("quality produced", quality is not None)
check("fixture TestPassRate = 100 (4/4 held-out)", quality and quality["test_pass_rate"] == 100.0)
check("Q_quality = 100 (masked: only test_pass present)", quality and quality["q_quality"] == 100.0)
check("agent self-test kept as separate signal", quality and quality["agent_test_pass_rate"] == 100.0)
check("code_review/user_acceptance remain uncollected (None)",
quality and quality["code_review_score"] is None and quality["user_acceptance"] is None)
# record + simulate a 30s run so V_speed is computable
await swarm_runtime.record_quality(run, quality)
run.created_at = run.updated_at - 30.0
await swarm_runtime.save_run(run)
collector = SwarmRunMetricsCollector(run.swarm_id)
m = await collector.collect()
check("reward is now REAL (not NaN)", not math.isnan(m.reward) and collector.coverage["reward"] is True)
# gain/p_decision/benchmark must still be NaN — Group B does not close them
check("gain still NaN (needs baselines)", math.isnan(m.s_gain) and collector.coverage["s_gain"] is False)
check("benchmark still NaN (aggregate)", math.isnan(m.benchmark) and collector.coverage["benchmark"] is False)
print()
if failures:
print(f"{len(failures)} quality check(s) FAILED: {failures}")
sys.exit(1)
print("all Group B quality checks passed")
if __name__ == "__main__":
asyncio.run(main())