harness2: upstream gap fill — trivial selection fix, session budget, run_tag validation, preflight check
- select_final trivial condition now k==1 and single-candidate pool, regardless of whether a selector is provided (previously gated on selector is None, so wired runs always paid for a useless selector task at k=1) - Budget gains max_sessions gate (probe + stage-attempt sessions; fmt2 corrections and selector/reflector sessions exempt), the backstop for unpriced models where the USD gate never binds - validate_run_tag (charset, no leading '-', no s<N> seed prefix) enforced at run_improvement_via_tasks and the CLI - CLI --check preflight (base harness, source dir, prompt, tag, control-plane /health probe) without creating tasks - ModeResult.to_dict now includes rollout summaries so a lost probe is visible in full_result instead of only on the server - CLI plan counts the selector only when k>1
This commit is contained in:
1 parent
3d60f32bd3
commit
4ed1479d7a
5 files changed
+247
-26
No files matched your search
+66
-2
@@ -62,6 +62,7 @@ parallel 一轮 = 1 base + 2 候选 + 1 终版探针 + 3 编辑 + 1 选择 +
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
@@ -91,6 +92,67 @@ DEFAULT_TASK_TIMEOUT_SECONDS = 3600
|
||||
DEFAULT_POLL_SECONDS = 10
|
||||
TRACE_MAX_CHARS = 50000
|
||||
|
||||
_TAG_RX = re.compile(r"[A-Za-z0-9_.][A-Za-z0-9_.\-]*\Z")
|
||||
_SEED_TAG_RX = re.compile(r"s\d+(-|\Z)")
|
||||
|
||||
|
||||
def validate_run_tag(tag: str) -> None:
|
||||
"""校验 run_tag 形状(上游 schedule.validate_tag 的对照补齐)。
|
||||
|
||||
run_tag 会进幂等键与路径段:空串表示“调用方稍后生成”,不
|
||||
校验;非空时只允许字母数字与 _ . -、首字符不能是 -、不能
|
||||
以 s + 数字开头(上游用 s<n> 段表示 seed,撞名会把两轮的
|
||||
产物算到同一命名空间)。
|
||||
"""
|
||||
if not tag:
|
||||
return
|
||||
if not _TAG_RX.match(tag):
|
||||
raise ValueError(
|
||||
f"run_tag {tag!r} must match [A-Za-z0-9_.-] with no leading '-' "
|
||||
"and no '/'")
|
||||
if _SEED_TAG_RX.match(tag):
|
||||
raise ValueError(
|
||||
f"run_tag {tag!r} must not start with a seed segment like 's1'")
|
||||
|
||||
|
||||
def preflight_checks(*, base_harness_dir: str | Path, source_dir: str | Path | None = None,
|
||||
prompt: str = "", run_tag: str = "",
|
||||
base_url: str | None = None) -> tuple[list[str], list[str]]:
|
||||
"""建任务前的本地 + 可达性预检(2026-10-29 对照补齐)。
|
||||
|
||||
返回 (errors, warnings):errors 非空时调用方不得建任务;
|
||||
warnings 只告警(health 探针失败、run_tag 为空待生成等不
|
||||
拦任务)。本函数不读密钥、不建任务,--check 可离线跑。
|
||||
"""
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
base_dir = Path(base_harness_dir)
|
||||
agents = base_dir / "AGENTS.md"
|
||||
if not base_dir.is_dir():
|
||||
errors.append(f"base-harness-dir 不存在或不是目录:{base_harness_dir}")
|
||||
elif not agents.is_file() or not agents.read_text(
|
||||
encoding="utf-8", errors="replace").strip():
|
||||
errors.append(f"base-harness-dir 里没有可用的 AGENTS.md:{base_harness_dir}")
|
||||
if source_dir is not None and str(source_dir) != "":
|
||||
if not Path(source_dir).is_dir():
|
||||
errors.append(f"source-dir 不存在或不是目录:{source_dir}")
|
||||
if not (prompt or "").strip():
|
||||
errors.append("需要 --prompt 或 --prompt-file(任务提示为空)")
|
||||
if run_tag:
|
||||
try:
|
||||
validate_run_tag(run_tag)
|
||||
except ValueError as exc:
|
||||
errors.append(str(exc))
|
||||
if base_url:
|
||||
try:
|
||||
req = urllib.request.Request(base_url.rstrip("/") + "/health")
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
if resp.status != 200:
|
||||
warnings.append(f"控制面 health 非 200:{resp.status}")
|
||||
except Exception as exc:
|
||||
warnings.append(f"控制面 health 探针失败(不拦任务):{type(exc).__name__}: {exc}")
|
||||
return errors, warnings
|
||||
|
||||
|
||||
def unwrap_result_text(raw: str) -> str:
|
||||
"""把服务端 result_text 的 JSON 信封拆开,只取 agent 的最终回复。
|
||||
@@ -709,6 +771,7 @@ def run_improvement_via_tasks(
|
||||
mode: str = "parallel",
|
||||
library_root: Path | None = None,
|
||||
budget_usd: float | None = None,
|
||||
max_sessions: int | None = None,
|
||||
model: str | None = None,
|
||||
effort: str | None = None,
|
||||
approval: str | None = None,
|
||||
@@ -739,10 +802,11 @@ def run_improvement_via_tasks(
|
||||
selector = CodexSelector(client=client, project_id=project_id, run_tag=run_tag, **shared)
|
||||
reflector = CodexReflector(client=client, project_id=project_id, run_tag=run_tag, **shared)
|
||||
library = Library(library_root, domain) if library_root is not None else None
|
||||
validate_run_tag(run_tag)
|
||||
return improve_task(
|
||||
adapter, editor, selector=selector, reflector=reflector,
|
||||
work_dir=work_dir, k=k, mode=mode, library=library,
|
||||
auto_apply=False, budget_usd=budget_usd)
|
||||
auto_apply=False, budget_usd=budget_usd, max_sessions=max_sessions)
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -750,5 +814,5 @@ __all__ = [
|
||||
"TRACE_MAX_CHARS", "TaskState", "TaskClient", "build_multipart",
|
||||
"HttpTaskClient", "CompletedTask", "run_task",
|
||||
"CodexProbeAdapter", "CodexEditor", "CodexSelector", "CodexReflector",
|
||||
"run_improvement_via_tasks",
|
||||
"run_improvement_via_tasks", "validate_run_tag", "preflight_checks",
|
||||
]
|
||||
Reference in new issue
Block a user