Files
Agentswarm/orchestrator/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

140 lines
5.9 KiB
Python

"""Quality instrumentation (benchmark Group B): turn a finished run's generated code into a real
TestPassRate, then into Q_quality via the masked/renormalized quality_score.
Pipeline:
1. gather the files the specialist agents generated (from task results) — split implementation
files from the swarm's own test files.
2. grade against the fixture's HELD-OUT tests (authoritative) in the sandbox → TestPassRate.
3. separately run the swarm's OWN tests as a non-grading signal (collaboration/robustness only).
4. Q_quality = quality_score(test_pass_rate=<fixture>, code_review_score=None, user_acceptance=None).
CodeReview / UserAcceptance are not collected here, so the masking rule renormalizes Q_quality
onto the one present input (rule #9: absent ≠ fabricated 0).
SECURITY: step 2/3 execute model-generated code. They run ONLY when ENABLE_QUALITY_EVAL is set,
and only inside the isolated pod (see orchestrator/sandbox.py security model). Default OFF.
"""
from __future__ import annotations
import asyncio
import json
import logging
import os
from typing import Any, Dict, List, Optional
from benchmark.fixtures import Fixture, load_fixture
from benchmark.metrics import quality_score
from .sandbox import SandboxFile, SandboxIsolationError, assert_isolated, run_tests
logger = logging.getLogger(__name__)
def quality_eval_enabled() -> bool:
return os.getenv("ENABLE_QUALITY_EVAL", "false").lower() in {"1", "true", "yes"}
def assert_quality_eval_safe() -> None:
"""Fail-closed startup check: if quality eval is ON, isolation MUST be confirmed.
Called at orchestrator startup so a misconfiguration (eval enabled, isolation not confirmed)
is a LOUD hard failure at boot — not a silent per-run skip and not an ops-only convention.
"""
if quality_eval_enabled():
assert_isolated() # raises SandboxIsolationError if HEICODE_SANDBOX_ISOLATED is unset
def _is_test_file(path: str) -> bool:
base = os.path.basename(path or "")
return base.startswith("test_") and base.endswith(".py")
def collect_generated_files(tasks) -> Dict[str, List[SandboxFile]]:
"""Reconstruct files the agents wrote, from task results. Last writer wins per path.
Returns {"impl": [...non-test files...], "agent_tests": [...the swarm's own test_*.py...]}.
"""
impl: Dict[str, str] = {}
agent_tests: Dict[str, str] = {}
for task in tasks:
result = getattr(task, "result", None)
if not result:
continue
try:
data = json.loads(result) if isinstance(result, str) else result
except Exception:
continue
subtasks = data.get("subtasks") if isinstance(data, dict) else None
file_groups = [data] + list(subtasks or []) if isinstance(data, dict) else []
for group in file_groups:
for f in (group.get("files") or []):
if not isinstance(f, dict):
continue
if (f.get("action") or "write") == "delete":
continue
path = f.get("path")
content = f.get("content")
if not path or content is None:
continue
(agent_tests if _is_test_file(path) else impl)[path] = content
return {
"impl": [SandboxFile(p, c) for p, c in impl.items()],
"agent_tests": [SandboxFile(os.path.basename(p), c) for p, c in agent_tests.items()],
}
async def evaluate_run_quality(run, tasks) -> Optional[Dict[str, Any]]:
"""Grade a completed run's code against its fixture. Returns a quality dict or None.
None means: quality eval disabled, no fixture bound, or nothing to grade — caller leaves
Q_quality/reward NaN (coverage=False). Never fabricates a score.
"""
if not quality_eval_enabled():
return None
# Fail-closed: never execute generated code without confirmed isolation, even if a fixture
# is bound and eval is enabled. Surface loudly rather than silently skipping.
assert_isolated() # raises SandboxIsolationError if HEICODE_SANDBOX_ISOLATED is unset
fixture_id = (run.metadata or {}).get("benchmark_fixture_id")
if not fixture_id:
return None
try:
fixture: Fixture = load_fixture(fixture_id)
except Exception as exc:
logger.warning("quality eval: cannot load fixture %s: %s", fixture_id, exc)
return None
files = collect_generated_files(tasks)
impl = files["impl"]
agent_tests = files["agent_tests"]
quality: Dict[str, Any] = {
"fixture_id": fixture.id,
"expects_code": fixture.expects_code,
"test_pass_rate": None,
"code_review_score": None, # not collected (no reviewer wired)
"user_acceptance": None, # not collected (external Manager/human signal)
"agent_test_pass_rate": None, # SIGNAL ONLY — never part of the grade
"target_time_seconds": fixture.target_time_seconds,
}
# 1) Authoritative grade: held-out fixture tests against the implementation files.
if fixture.expects_code and fixture.test_files and impl:
fix_files = [SandboxFile(t.path, t.content) for t in fixture.test_files]
result = await asyncio.to_thread(run_tests, impl, fix_files)
quality["test_pass_rate"] = result.pass_rate
quality["fixture_total"] = result.total
quality["fixture_passed"] = result.passed
if result.error:
quality["fixture_error"] = result.error
# 2) Signal only: the swarm's own tests (collaboration/robustness, NOT the grade).
if agent_tests and impl:
sig = await asyncio.to_thread(run_tests, impl, agent_tests)
quality["agent_test_pass_rate"] = sig.pass_rate
# 3) Q_quality via the masked/renormalized blend (absent inputs drop out).
quality["q_quality"] = quality_score(
test_pass_rate=quality["test_pass_rate"],
code_review_score=quality["code_review_score"],
user_acceptance=quality["user_acceptance"],
)
return quality