Skip to content

SPIKE: exec-approvals architecture — gut decorative channel-forwarding fields or wire AgentRuntime authority bridge #2573

@alexey-pelykh

Description

@alexey-pelykh

Background

Investigation of the gateway's exec-approvals subsystem during disposition cleanup surfaced an architectural inconsistency that needs a decision before the next release cycle.

Two paths exist; only one is wired:

  1. Working: tools/nodes-tool.ts (alive MCP tool) calls callGatewayTool("exec.approval.request", ...) over RPC when a node-host command needs approval. Gateway broadcasts exec.approval.requested to subscribers (server-broadcast.ts:13). The macOS companion app subscribes, renders the approval UI, user clicks, response flows back via gateway → agent unblocks (120s timeout). End-to-end functional.

  2. Decorative: forwardExecApprovals fields are declared in types.discord.ts:143 and types.telegram.ts:62 (per-account "forward exec approvals to this discord/telegram thread"). Zero consumers in src/ read these fields. A user setting forwardExecApprovals.enabled = true in their telegram/discord config gets nothing — the gateway broadcast does not translate into a channel message.

Larger context: The AgentRuntime currently spawns CLI agents with --dangerously-skip-permissions (per src/middleware/channel-bridge.test.ts:1111), which bypasses each CLI agent's own approval prompt mechanism (Claude Code, Gemini CLI, etc. all have built-in approve/deny flows). This means:

  • The CLI agent's own bash/edit/write tool approvals never reach the user — they're auto-approved by the bypass flag
  • Only the gateway-side tools/nodes-tool.ts MCP path goes through exec.approval.request
  • The forwardExecApprovals channel-forwarding promise is the (dead) bridge that would have closed this gap for channel-based UX

The architectural question this surfaces: does the AgentRuntime own the authority bridge between user-facing channels (discord/telegram/macOS app) and CLI agents' own permission prompts?

Spike scope

Reach a decision (with ADR or design note) on which path to commit to:

Path A: Gut decorative fields (small, mechanical)

Accept "AgentRuntime is a thin spawn-wrapper" as the architectural decision. Document that:

  • CLI agents own their own permission UX internally (which is bypassed for headless-via-gateway, intentionally)
  • tools/nodes-tool.ts gateway-side approvals → macOS companion app remains the only mediated approval path
  • Channel-based exec-approval forwarding is a non-goal

Then delete the decorative schema fields:

  • forwardExecApprovals in src/config/types.discord.ts (~lines 143-145)
  • forwardExecApprovals in src/config/types.telegram.ts (~lines 62-68)
  • Related Zod schema definitions in src/config/zod-schema.approvals.ts (ExecApprovalForwardingSchema)
  • schema.base.generated.ts regeneration
  • Doc updates in docs/tools/exec-approvals.md to remove channel-forwarding references

Path B: Wire the bridge (larger scope)

Commit to AgentRuntime owning the authority bridge. Each CLI runtime (Claude, Gemini, Codex, OpenCode) has its own permission-prompt mechanism — Claude Code has hooks (PreToolUse), Gemini has its own flow, etc.

Investigation needed:

  • For each of the 4 CLI runtimes, identify the integration point for capturing approval prompts (hook? stdio interception? MCP middleware?)
  • Design the agent → gateway → channel/macOS-app routing for these prompts (likely reuse exec.approval.request RPC infra)
  • Wire forwardExecApprovals config consumer in each channel adapter (discord-bot listener for the approve/deny reply; telegram likewise)
  • Decide on UX: button-based approval in discord/telegram message, vs. plain-text reply parsing
  • Estimate: probably 2-4 PRs per CLI runtime + one shared infrastructure PR

Deliverables

This spike produces:

  1. ADR or design doc with the decision + rationale (committed to the repo or governance channel)
  2. If Path A: a follow-up gut-deletion PR (mechanical, ~5 files)
  3. If Path B: a sequence of follow-up issues breaking down the wire-up work per CLI runtime + shared infra

Suggested investigation order

  1. Determine each CLI runtime's permission-prompt API: Claude Code (hooks), Gemini (config flag/hook?), Codex (TBD), OpenCode (TBD)
  2. Estimate Path B effort per runtime
  3. Compare against the value of channel-based approvals UX vs. macOS-app-only
  4. Decide

Out of scope

  • The doc docs/tools/exec-approvals.md stays for now (correctly describes the working macOS-app-mediated path); rewriting if Path A wins is part of the deletion PR
  • bash-tools.exec-approval-followup.ts is dead and slated for deletion separately
  • Memory plugin slot and SecretRef wiring gaps are tracked as separate issues

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions