fix(agent-harness): structured policy-block messages — rework of #4390 onto post-#4399#4443
Conversation
📝 WalkthroughWalkthroughAdds a new ChangesPolicyDenial structured messaging
Estimated code review effort: 2 (Simple) | ~15 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant ToolPolicyMiddleware
participant PolicyDenial
Caller->>ToolPolicyMiddleware: invoke tool call
ToolPolicyMiddleware->>ToolPolicyMiddleware: evaluate policy/permission
alt session forbidden or permission too low
ToolPolicyMiddleware->>PolicyDenial: construct SessionForbidden/PermissionTooLow
else require approval or policy denied
ToolPolicyMiddleware->>PolicyDenial: construct ApprovalRequired/PolicyDenied
end
PolicyDenial->>PolicyDenial: render()
PolicyDenial-->>ToolPolicyMiddleware: formatted denial string
ToolPolicyMiddleware-->>Caller: denial content
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3e6ba30d5d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "Raise the '{channel}' channel's agent-access tier to at least {required} \ | ||
| (Settings → Agent access, or the `config.update_autonomy_settings` RPC / \ | ||
| `[autonomy]` config), or accomplish the goal with a tool that needs only \ |
There was a problem hiding this comment.
Point permission denials at the channel permission knob
When a tool is blocked by the channel permission ceiling, this workaround sends the user to config.update_autonomy_settings / [autonomy], but the ceiling being enforced here comes from AgentConfig.channel_permissions via ToolPolicyEngine::build_session, not the autonomy access-mode config. In a read-only channel, a user can follow this instruction and switch readonly/supervised/full without changing the denial; they need the channel permission entry raised instead, so the new actionable message is misleading for exactly the permission-tier blocks it is meant to recover from.
Useful? React with 👍 / 👎.
Rework of tinyhumansai#4390 onto post-tinyhumansai#4399 code. tinyhumansai#4399 relocated the tool policy gate from the deleted session/agent_tool_exec.rs to the tinyagents adapter middleware (ToolPolicyMiddleware), which still emitted bare denial lines. - Ports the deleted policy_denial.rs into the adapter seam as src/openhuman/tinyagents/policy_denial.rs: a `PolicyDenial` renderer that turns each denial into a structured `Blocked / Reason / Workaround / relay` message (with the concrete "raise the channel's agent-access tier" or "permitted alternative / ask for approval" workaround) so the agent relays the block to the user instead of dead-ending. - Swaps the three bare `format!` denials in ToolPolicyMiddleware (channel_permission_block session-forbidden + per-call permission-too-low, and the pluggable-policy Deny/RequireApproval branch in wrap_tool) for PolicyDenial::render(). 5 unit tests for the renderer (session-forbidden with/without required, permission-too-low, policy-denied, approval-required).
3e6ba30 to
65cbd12
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/openhuman/tinyagents/middleware.rs (1)
1344-1356: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider matching
Denyexplicitly instead of_.The
blocked_actionmatch just above (Line 1326) explicitly listsRequireApproval,Deny, andAllow. Thecontentmatch here falls back to_ => PolicyDenial::PolicyDeniedfor anything that isn'tRequireApproval. Functionally correct today (onlyDenycan reach this branch), but ifToolPolicyDecisionever gains a new blocking variant, it would silently render asPolicyDeniedwithout an explicit design decision.♻️ Optional: explicit match arm
let content = match &decision { ToolPolicyDecision::RequireApproval { .. } => PolicyDenial::ApprovalRequired { tool: &call.name, policy: self.policy.name(), reason, }, - _ => PolicyDenial::PolicyDenied { + ToolPolicyDecision::Deny { .. } | ToolPolicyDecision::Allow => PolicyDenial::PolicyDenied { tool: &call.name, policy: self.policy.name(), reason, }, } .render();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/tinyagents/middleware.rs` around lines 1344 - 1356, The `content` match in `middleware.rs` is too broad by using `_` for all non-`RequireApproval` cases, which hides the intended handling for blocking decisions. Update the match on `decision` to explicitly handle `ToolPolicyDecision::Deny` alongside `ToolPolicyDecision::RequireApproval`, and keep the `PolicyDenial::PolicyDenied` rendering only for that explicit deny case. Use the nearby `blocked_action` match and the `PolicyDenial` construction in this branch as the reference points while preserving the existing behavior for `RequireApproval`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/openhuman/tinyagents/middleware.rs`:
- Around line 1344-1356: The `content` match in `middleware.rs` is too broad by
using `_` for all non-`RequireApproval` cases, which hides the intended handling
for blocking decisions. Update the match on `decision` to explicitly handle
`ToolPolicyDecision::Deny` alongside `ToolPolicyDecision::RequireApproval`, and
keep the `PolicyDenial::PolicyDenied` rendering only for that explicit deny
case. Use the nearby `blocked_action` match and the `PolicyDenial` construction
in this branch as the reference points while preserving the existing behavior
for `RequireApproval`.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 76cebb7b-2a60-4721-95ae-f2970e116956
📒 Files selected for processing (3)
src/openhuman/tinyagents/middleware.rssrc/openhuman/tinyagents/mod.rssrc/openhuman/tinyagents/policy_denial.rs
Summary
Rework of #4390 onto post-#4399
main. When a tool call is blocked at a policy/permission boundary, the agent now gets a structured Blocked / Reason / Workaround / relay-to-user message instead of a bare denial line — so it relays the block and offers a concrete next step rather than dead-ending.Why a rework (not a rebase)
#4399 (the TinyAgents harness migration) deleted
session/agent_tool_exec.rsandsession/policy_denial.rswhere the original #4390 lived, and relocated the tool policy gate onto the tinyagents adapter middleware (ToolPolicyMiddlewareinsrc/openhuman/tinyagents/middleware.rs), which still emitted the bare denial strings. So #4390 can't rebase — it's re-expressed against the current seam.Changes
src/openhuman/tinyagents/policy_denial.rs— ports the deleted renderer: aPolicyDenialenum (SessionForbidden/PermissionTooLow/PolicyDenied/ApprovalRequired) withrender()producingBlocked: … Reason: … Workaround: … Relay this to the user: …. Permission-tier denials point at the real knob ([autonomy]/config.update_autonomy_settings, channel agent-access tier), not a vague "increase permissions".ToolPolicyMiddleware— the three bareformat!denials (session-forbidden + per-call permission-too-low inchannel_permission_block, and the pluggable-policyDeny/RequireApprovalbranch inwrap_tool) now callPolicyDenial::render().Tests
5 unit tests for the renderer (all four variants + the with/without-
requiredsession-forbidden paths).cargo test --lib policy_denialgreen.Related
main(the feat: finish TinyAgents harness migration (#4249) #4399 break lives inmain); merge fix(orchestration): repair TinyAgents-migration build break (main does not compile) #4442 first, then this rebases clean and green.Summary by CodeRabbit
New Features
Bug Fixes