Repository navigation
Add fail-closed goal verifier to completion resolution - #281
raymondginger2018-sudo wants to merge 1 commit into
Conversation
|
Thanks for putting this together, @raymondginger2018-sudo. Checking a goal's completion against evidence is something we care about, but we can't merge this version. When an async verifier rejects (including Nothing in the app registers a verifier, so the feature would ship switched off. Users can already veto completion today with a A version we could accept would:
If that's more than you want to take on, we'll close this for now. Thanks again! |
…idence, blocked-after-N, drop unused classes Address @Zongwei9888's five review points on the goal completion verifier: 1. Verifier runs exactly once, asynchronously, before any durable update. GoalRuntimeRouter.request() is now async and awaits the verifier once; there is no 'verified' bypass parameter. A rejection returns data and never calls handler.update_goal, so no completion is recorded. A sync verifier that raises is caught by the same try and fails closed (rejects) instead of escaping as a raw RuntimeError. 2. The claim handed to the verifier carries the real goal objective (no longer hardcoded empty) plus this Turn's existing evidence references, via GoalExtension.completion_request(), reusing the outcome's evidence filter and caps. 3. A rejection returns the verifier's justification to the model as a structured dict; after max_rejections (default 3) the router itself moves the Goal to blocked and freezes the Turn instead of letting the model burn Turns retrying. 4. Real caller: build_agent_session arms RuleBasedVerifier when config.goal.completion_verifier == 'rule' (new GoalConfig, default 'off' preserves existing behavior). E2e test drives UpdateGoalTool through the ToolRegistry with an async verifier that rejects. 5. Drop the unused GoalVerifierRejected and GoalCompletionVerifierPolicy classes and the .verify protocol; goal_verifier.py is stdlib-only, import-inert, and has no references to other products.
618ccdc to
d347065
Compare
|
Hi @Zongwei9888, Thanks for the detailed review — the double-call and 1. Verifier called exactly once, asynchronously, no
if status == "complete" and verifier is not None:
try:
verdict = await verifier(handler.completion_request(context, reason=reason))
except Exception as exc:
verdict = GoalVerdict(confirmed=False, justification=f"Completion verification failed: {exc}")
if not verdict.confirmed:
return self._reject_completion(handler, context, verdict)There is no 2. Real objective + the Turn's existing evidence
3. Justification returned to the model; blocked after N rejections A rejection returns a structured dict to the model — 4. Real caller + e2e test through
5. Dropped unused classes and other-product references
The documented Local checks: Thanks again for the careful review. |
Summary
Closes the goal-completion verify loop: a registered async verifier decides whether a goal request may complete, and rejections keep the agent working instead of falsely completing. Borrowed from harness-style goal verification. Dormant unless a verifier is configured — with none registered, behavior is exactly as before.
Changes
core/agent_runtime/goal_verifier.py:GoalRuntimeRouter.configure_completion_verifierregistration surface plus verdict normalization helpers.core/agent_runtime/goal_runtime.py:resolve_completion(...)awaits the registered verifier (handles both coroutine and sync verdicts), then feeds the outcome intorequest(verified=...).GoalRuntimeError("...keep working.")so the turn continues.core/harness/tools/goal.py: pass the verifier outcome through the goal tool response.tests/test_goal_completion_verifier.py(approval, rejection, exception, empty-evidence, no-verifier paths).Tests
tests/test_goal_completion_verifier.py+tests/test_goal_runtime.py— 19 passedpre-commit run --all-files(ruff 0.15.21) — passedNotes