hermes: separate review findings from blockers
This commit is contained in:
parent
42ac76fe67
commit
1ea8e16286
@ -25,7 +25,7 @@ spec:
|
||||
ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers
|
||||
ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback
|
||||
ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
|
||||
ai.bstein.dev/config-rev: "20260816-goal-completion-judge-v2"
|
||||
ai.bstein.dev/config-rev: "20260816-review-findings-contract-v3"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: /metrics
|
||||
prometheus.io/port: "9010"
|
||||
|
||||
@ -70,7 +70,22 @@ RESULT_SCHEMA: dict[str, Any] = {
|
||||
"changed_files": {"type": "array", "items": {"type": "string"}},
|
||||
"tests_run": {"type": "array", "items": {"type": "string"}},
|
||||
"artifacts": {"type": "array", "items": {"type": "string"}},
|
||||
"blockers": {"type": "array", "items": {"type": "string"}},
|
||||
"findings": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": (
|
||||
"Defects, risks, or observations discovered by a review or diagnosis. "
|
||||
"Findings do not prevent the assigned review or diagnosis from completing."
|
||||
),
|
||||
},
|
||||
"blockers": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": (
|
||||
"Concrete obstacles that prevent completion of the assigned task itself. "
|
||||
"This must be empty when status is completed; review findings belong in findings."
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -277,7 +292,7 @@ Workspace: {workspace}
|
||||
|
||||
Operate autonomously inside the workspace. Inspect before editing, preserve unrelated user changes, run proportionate tests, and do not claim completion without evidence. You have owner-level Kubernetes access in every namespace. Prefer Flux-tracked manifests for durable changes, but use kubectl, Flux, exec, port-forwarding, rollout operations, and existing Vault workflows when the objective or incident requires them. Persist any desired-state mutation back to Git. Do not force-push, hard-reset, clean untracked files, or expose credentials.
|
||||
|
||||
Return a final JSON object matching the supplied schema. Use status=incomplete when required work, tests, commands, commits, pushes, or verification are still running or remain to be done. Use status=blocked only for a concrete unresolved blocker. Never use status=completed for a progress report. List changed files, tests run, durable artifact paths, and blockers explicitly.
|
||||
Return a final JSON object matching the supplied schema. Use status=incomplete when required work, tests, commands, commits, pushes, or verification are still running or remain to be done. Use status=blocked only when an obstacle prevents completion of the assigned task itself. Never use status=completed for a progress report. For review or diagnostic tasks, put defects and risks in findings; those findings can make the reviewed change unfit to ship without blocking completion of the review. The blockers array must be empty whenever status is completed. List changed files, tests run, durable artifact paths, findings, and task blockers explicitly.
|
||||
"""
|
||||
|
||||
|
||||
@ -964,7 +979,13 @@ def execute_claim(board: str, task_id: str) -> None:
|
||||
"goal_turn": goal_turn,
|
||||
}
|
||||
if structured:
|
||||
for key in ("changed_files", "tests_run", "artifacts", "blockers"):
|
||||
for key in (
|
||||
"changed_files",
|
||||
"tests_run",
|
||||
"artifacts",
|
||||
"findings",
|
||||
"blockers",
|
||||
):
|
||||
value = structured.get(key)
|
||||
metadata[key] = value if isinstance(value, list) else []
|
||||
|
||||
|
||||
@ -53,6 +53,15 @@ def test_completed_pending_state_test_name_is_not_a_false_positive():
|
||||
assert goal.unfinished_result_reason(result) is None
|
||||
|
||||
|
||||
def test_completed_review_findings_are_not_task_blockers():
|
||||
result = _result(
|
||||
summary="Review complete; the pull request is not merge-ready.",
|
||||
findings=["A casting-table variant fails open."],
|
||||
)
|
||||
|
||||
assert goal.unfinished_result_reason(result) is None
|
||||
|
||||
|
||||
class JudgeResponse:
|
||||
def __init__(self, verdict: str, reason: str):
|
||||
self.body = json.dumps(
|
||||
|
||||
@ -946,6 +946,15 @@ def test_provider_commands_are_structured_unattended_and_capped(tmp_path: Path):
|
||||
assert "max" not in claude
|
||||
|
||||
|
||||
def test_worker_contract_separates_review_findings_from_task_blockers(tmp_path: Path):
|
||||
prompt = lanes.build_prompt("Review the change.", tmp_path)
|
||||
|
||||
assert "put defects and risks in findings" in prompt
|
||||
assert "blockers array must be empty whenever status is completed" in prompt
|
||||
assert "findings" in lanes.RESULT_SCHEMA["properties"]
|
||||
assert "assigned task itself" in lanes.RESULT_SCHEMA["properties"]["blockers"]["description"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user