Skip to content

feat(core): optional plugin gate in PermissionV2 for allow→ask - #34329

Open
thomaslwang wants to merge 2 commits into
anomalyco:devfrom
thomaslwang:feat/permission-v2-plugin-gate
Open

feat(core): optional plugin gate in PermissionV2 for allow→ask#34329
thomaslwang wants to merge 2 commits into
anomalyco:devfrom
thomaslwang:feat/permission-v2-plugin-gate

Conversation

@thomaslwang

Copy link
Copy Markdown

Issue for this PR

Implements the consultation point requested in #34327.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

PermissionV2 is where every tool (bash, edit, webfetch, read, …) resolves a
permission to allow/ask/deny. A plugin today can only observe a tool call
via tool.execute.before, which runs after permission resolution and can deny
only by throwing — there's no way for a plugin to turn a would-auto-approve
call into an interactive prompt.

This adds an optional Gate service that evaluateInput consults right after the
effect resolves (and only on the non-hard-deny path). A provider can downgrade an
allow to ask/deny, or upgrade an ask to allow. It's read via
Effect.serviceOption, so PermissionV2 keeps no hard dependency on it and
the behavior is unchanged when nothing provides a Gate (the common case). The
service is defined in core and consumed in core; a host wires a provider.

Scope note: this is intentionally just the core hook point. Wiring a concrete
provider — e.g. one backed by the already-declared permission.ask plugin hook —
into PermissionV2's location-scoped runtime touches the location-service
composition, so I've left that out for maintainer guidance rather than guess at
the layering. Happy to follow up with the provider once the approach is agreed.

Motivation: I maintain an external OGR guardrails plugin that evaluates tool calls
against agent-configured rules; today it can only block (via tool.execute.before)
and a first-class ask from a plugin would let it request human confirmation.

How did you verify your code works?

tsgo --noEmit is clean for @opencode-ai/core. The change is additive and
no-op without a registered Gate (verified the serviceOption path leaves the
existing effect untouched). No existing behavior changes.

Screenshots / recordings

N/A — no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Thomas and others added 2 commits June 29, 2026 00:23
Add an optional Gate service that PermissionV2 consults (via
Effect.serviceOption, so it stays a no-op with no hard dependency) right after a
permission effect resolves. A provider can downgrade a would-auto-approve
`allow` to `ask`/`deny`, or upgrade an `ask` to `allow`. Hard `deny` rules are
not routed through it.

This is the consultation point requested in anomalyco#34327; it lets a guardrails plugin
turn a would-auto-approve tool call into an interactive prompt — something
tool.execute.before cannot do (it can only throw to deny).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thomaslwang

Copy link
Copy Markdown
Author

@jlongster gentle nudge on this one (you're assigned to the tracking issue #34327) — no rush.

Quick recap of the ask: this adds an optional Gate service in PermissionV2's evaluateInput, consulted right after the effect resolves, so a plugin can turn a would-auto-approve into an interactive ask. It's a no-op with no hard dependency when no provider is registered, so it's inert unless something opts in. The PR is honestly scoped to just the core hook point — wiring the permission.ask plugin hook through it touches core's location-service layering, which I left as a maintainer call.

Context for why it's useful: it's the one interception tool.execute.before can't do (block-only, no human prompt). I'm using it to drive a vendor-neutral guardrail layer (OpenGuardrails) — same enforcement point I'm wiring into Kilo CLI (Kilo-Org/kilocode#11619).

State: mergeable, checks green, conventional title + compliance template in place. Happy to adjust the seam (naming, where it's consulted, the provider contract) if you'd prefer a different shape.

@ThinkDonk

Copy link
Copy Markdown

Thanks for this — this is exactly the hook point I've been looking for.

I discovered the same gap while exploring how to build a plugin that auto-approves external_directory requests when the target is a git-managed repo (and injects git-commit reminders afterwards). #7006 describes the V1 path issue, but the V2 path was the real dead end.

One architectural suggestion: would it simplify the wiring if Gate were defined as a global-scoped service rather than location-scoped? Effect.serviceOption resolves upward through layers, so a Gate provided at the global level (in opencode's server.ts) would be visible inside evaluateInput without needing to touch the locationServices monolith or wrestle with the hoist/compile machinery. This also cleanly separates concerns: core defines the contract, opencode provides the plugin-backed implementation.

Once this merges, I'd be happy to submit the follow-up PR wiring the permission.ask plugin hook through Gate. Happy to coordinate or defer to whatever approach the maintainers prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants