fix(agent): enforce approval gate on allowFinalResponse path and normalize approval-predicate args#55
Conversation
…alize approval-predicate args The allowFinalResponse path previously executed the pending tool calls left over when stopWhen halted the loop without first running the approval partitioning the main loop applies to every other round. It now runs the normal approval check before executing those pending calls, so an approval-gated tool can no longer run unprompted on the final-response turn. Separately, function-form requireApproval predicates were evaluated against the raw model-produced arguments while the tool's execute() received the Zod-parsed input (defaults applied, values coerced). The predicate now sees the schema-normalized arguments, so the approval decision matches the input the tool actually executes with.
d3569e7 to
ad9c2a3
Compare
There was a problem hiding this comment.
Perry's Review
Fixes two approval-gate gaps: executes the approval check before running pending tool calls on the allowFinalResponse path, and normalizes tool-call arguments via schema-parsing before evaluating function-form requireApproval predicates.
Verdict: 💬 Comments / questions
Details
Risk: 🟡 Medium — approval-gate behavioral change in agent loop; confined to callers using requireApproval, well-tested
CI: no checks reported on branch
Findings:
- No new findings since prior review (diff identical to
d3569e7) - 2 prior suggestions remain open (see existing review threads below)
Security: no concerns — categories 1–8 not touched; library-internal approval-predicate change
Test coverage: new tests cover allowFinalResponse approval path and schema-defaults normalization for tool-level predicates; callLevelCheck path for schema-normalized args is still untested (open thread)
Unresolved threads: 2 open Perry suggestion threads from prior review
Review metadata
Scope: incremental — 0 new lines since prior review (diff unchanged from d3569e7)
Review: tier=small · model=claude-sonnet-latest · score=1.5
TL;DR
Fixes two approval-gate gaps in the agent loop (only affects apps that use
requireApproval). Closes #54.allowFinalResponseskipped the approval check. WhenstopWhenhalts the loop on a turn that still has pending tool calls andallowFinalResponseis enabled, those calls were executed viaexecuteToolRound(...)directly — without thehandleApprovalCheck/partitionToolCallsstep the main loop runs before every other round. An approval-gated tool could therefore run unprompted on the final-response turn. This PR runs the normal approval check before executing those pending calls.requireApprovalpredicates received the raw model-produced arguments, whileexecute()received the Zod-parsed input (defaults applied, values coerced). The predicate now sees the schema-normalized arguments, so the approval decision matches what the tool actually runs with.Files
packages/agent/src/lib/model-result.ts,packages/agent/src/lib/conversation-state.tspackages/agent/tests/unit/allow-final-response.test.ts(adds the first approval-path coverage for this branch),packages/agent/tests/unit/conversation-state.test.ts.changeset/approval-gate-final-response.md(patch)Risk / reviewer notes
pnpm run lint(Biome),pnpm run typecheck,pnpm run test(297/297). I could not run thesentruxstructural-gate job locally; the change is small and stays within thelib/layer, but please confirm that gate in CI.Background and a fuller write-up are in #54.