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:
-
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.
-
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:
- ADR or design doc with the decision + rationale (committed to the repo or governance channel)
- If Path A: a follow-up gut-deletion PR (mechanical, ~5 files)
- If Path B: a sequence of follow-up issues breaking down the wire-up work per CLI runtime + shared infra
Suggested investigation order
- Determine each CLI runtime's permission-prompt API: Claude Code (hooks), Gemini (config flag/hook?), Codex (TBD), OpenCode (TBD)
- Estimate Path B effort per runtime
- Compare against the value of channel-based approvals UX vs. macOS-app-only
- 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
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:
Working:
tools/nodes-tool.ts(alive MCP tool) callscallGatewayTool("exec.approval.request", ...)over RPC when a node-host command needs approval. Gateway broadcastsexec.approval.requestedto 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.Decorative:
forwardExecApprovalsfields are declared intypes.discord.ts:143andtypes.telegram.ts:62(per-account "forward exec approvals to this discord/telegram thread"). Zero consumers insrc/read these fields. A user settingforwardExecApprovals.enabled = truein 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(persrc/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:bash/edit/writetool approvals never reach the user — they're auto-approved by the bypass flagtools/nodes-tool.tsMCP path goes throughexec.approval.requestforwardExecApprovalschannel-forwarding promise is the (dead) bridge that would have closed this gap for channel-based UXThe 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:
tools/nodes-tool.tsgateway-side approvals → macOS companion app remains the only mediated approval pathThen delete the decorative schema fields:
forwardExecApprovalsinsrc/config/types.discord.ts(~lines 143-145)forwardExecApprovalsinsrc/config/types.telegram.ts(~lines 62-68)src/config/zod-schema.approvals.ts(ExecApprovalForwardingSchema)schema.base.generated.tsregenerationdocs/tools/exec-approvals.mdto remove channel-forwarding referencesPath 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:
exec.approval.requestRPC infra)forwardExecApprovalsconfig consumer in each channel adapter (discord-bot listener for the approve/deny reply; telegram likewise)Deliverables
This spike produces:
Suggested investigation order
Out of scope
docs/tools/exec-approvals.mdstays for now (correctly describes the working macOS-app-mediated path); rewriting if Path A wins is part of the deletion PRbash-tools.exec-approval-followup.tsis dead and slated for deletion separately