Skip to content

feat(gateway): add contextual rationale to approval prompts#27833

Open
zccyman wants to merge 1 commit into
NousResearch:mainfrom
atyou2happy:feat/approval-context-rationale-27604
Open

feat(gateway): add contextual rationale to approval prompts#27833
zccyman wants to merge 1 commit into
NousResearch:mainfrom
atyou2happy:feat/approval-context-rationale-27604

Conversation

@zccyman

@zccyman zccyman commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Include the agent's last assistant text as contextual rationale in dangerous command approval prompts across all gateway adapters.

When the agent requests approval for a dangerous command (e.g. rm -rf), the user now sees the agent's reasoning (e.g. "I need to free disk space before the build") above the command preview, giving context for the approval decision.

Closes #27604

Changes

  • gateway/run.py: Capture last assistant text in _interim_assistant_cb closure, pass to adapter send_exec_approval as contextual_reason
  • All 7 adapters (Telegram, Feishu, Discord, Slack, Matrix, QQBot, Teams): Accept contextual_reason: str = "" kwarg, render in platform-native approval UI
  • Graceful degradation: no rationale shown when interim messages disabled or assistant produced no text before tool call

Test Plan

  • 8 new tests in tests/gateway/test_approval_contextual_rationale.py
  • 253 total tests passing (8 new + 61 existing adapter approval + 184 core approval)
  • All 8 modified files pass syntax checks
  • Verified: Telegram HTML escaping, Feishu card markdown, Matrix text body, Slack Block Kit, Discord embed

Screenshots (conceptual)

Before:

⚠️ Command Approval Required
`rm -rf /tmp/cache`
Reason: dangerous command
[Allow Once] [Session] [Always] [Deny]

After:

⚠️ Command Approval Required
I need to free up disk space before the build.

`rm -rf /tmp/cache`
Reason: dangerous command
[Allow Once] [Session] [Always] [Deny]

Include the agent's last assistant text as contextual_reason in
dangerous command approval prompts across all gateway adapters.

When the agent requests approval for a dangerous command, the user
now sees the agent's reasoning (e.g. 'I need to free disk space')
above the command preview, giving context for the approval decision.

Changes:
- gateway/run.py: capture last assistant text in closure, pass to
  adapter send_exec_approval as contextual_reason
- All 7 adapters (Telegram, Feishu, Discord, Slack, Matrix, QQBot,
  Teams): accept contextual_reason kwarg, render in approval UI
- Graceful degradation: no rationale shown when interim messages
  disabled or assistant produced no text before tool call

Closes NousResearch#27604

@Bartok9 Bartok9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid implementation. A few observations:

Correctness — The _last_assistant_rationale closure captures the most recent non-empty text from _interim_assistant_cb, which is the right signal. One subtle case: if the agent emits a large streaming blob and the rationale is the first segment rather than the last, only the most recent chunk is preserved. In practice this is usually fine (the final reasoning summary is typically at the end), but worth a comment.

QQBot path — The QQBot adapter sends the rationale as a separate preceding message (await self.send(...)) rather than embedding it in the approval card. This is pragmatically correct (QQBot's card format is more rigid), but it creates a minor race: if the approval card arrives before the rationale message on slow connections. Consider awaiting self.send before sending the approval request (it looks like it already does — just confirming the send order is preserved).

Telegram HTML escape — Does contextual_reason get HTML-escaped before injection into Telegram's parse_mode=HTML messages? Worth confirming the Telegram path uses html.escape(contextual_reason) for the HTML-mode render path to avoid accidental tag injection from streaming text.

Tests — 8 tests covering all 7 adapters is good coverage. Would add one edge-case: rationale that itself contains Markdown/HTML special characters (backticks, <, >) to exercise the escaping paths.

Overall this is a clean, useful feature — good work.

@Bartok9

Bartok9 commented May 18, 2026

Copy link
Copy Markdown
Contributor

Solid implementation. A few observations:

Correctness — The _last_assistant_rationale closure captures the most recent non-empty text from _interim_assistant_cb, which is the right signal. One subtle case: if the agent emits a large streaming blob and the rationale is the first segment rather than the last, only the most recent chunk is preserved. In practice this is usually fine (the final reasoning summary is typically at the end), but worth a comment.

QQBot path — The QQBot adapter sends the rationale as a separate preceding message (await self.send(...)) rather than embedding it in the approval card. This is pragmatically correct (QQBot's card format is more rigid), but it creates a minor race: if the approval card arrives before the rationale message on slow connections. The await before the approval request preserves send order — looks correct.

Telegram HTML escape — Confirm contextual_reason gets HTML-escaped before injection into Telegram's parse_mode=HTML render path to avoid accidental tag injection from streaming text that contains <, >, or &.

Tests — 8 tests covering all 7 adapters is good coverage. One useful edge-case to add: rationale containing Markdown/HTML special characters (backticks, <, >) to exercise escaping paths.

Overall this is a clean, useful feature — good work.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter platform/discord Discord bot adapter platform/feishu Feishu / Lark adapter platform/slack Slack app adapter platform/matrix Matrix adapter (E2EE) platform/qqbot QQ Bot adapter P3 Low — cosmetic, nice to have labels May 18, 2026
@zccyman

zccyman commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @Bartok9!

Streaming rationale ordering: Good catch — the closure captures the most recent non-empty chunk, which is indeed the final reasoning summary in the common case. I'll add a docstring comment clarifying this behavior for future maintainers.

QQBot race condition: You're right that the separate send() for the rationale could theoretically arrive after the approval card on slow connections. In practice QQBot's WebSocket channel serializes within a session, but the defensive fix (embedding rationale in the card when possible) is cleaner long-term. I'll note this as a follow-up improvement.

Both points are non-blocking observations — appreciate the detailed analysis!

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/discord Discord bot adapter platform/feishu Feishu / Lark adapter platform/matrix Matrix adapter (E2EE) platform/qqbot QQ Bot adapter platform/slack Slack app adapter platform/telegram Telegram bot adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Approval prompts should include the agent's contextual rationale (like Codex)

3 participants