Skip to content

feat(engine): mode-agnostic system prompt with append-only mode/approval messages#2687

Draft
LeoAlex0 wants to merge 1 commit into
Hmbown:mainfrom
LeoAlex0:feat/cache-append-only
Draft

feat(engine): mode-agnostic system prompt with append-only mode/approval messages#2687
LeoAlex0 wants to merge 1 commit into
Hmbown:mainfrom
LeoAlex0:feat/cache-append-only

Conversation

@LeoAlex0
Copy link
Copy Markdown
Contributor

@LeoAlex0 LeoAlex0 commented Jun 3, 2026

Summary / 摘要

Make message[0] completely mode-agnostic by stripping mode_prompt(mode) and approval_prompt_for_mode(mode, …) from the base system prompt in prompts.rs. Mode instructions and approval policies are now delivered exclusively through deduplicated append-only system messages, keeping message[0] byte-stable across mode switches and approval-policy changes for DeepSeek KV prefix-cache hits.

prompts.rsmode_prompt(mode)approval_prompt_for_mode(mode, …) 从基础系统提示中移除,使 message[0] 完全模式无关。模式指令与审批策略通过去重的追加式 system 消息送达,保证 message[0] 在模式切换和审批策略变更后字节稳定。

Architecture / 架构

message[0] (system):   mode-agnostic base prompt — no mode or approval text
message[1] (system):   <mode_prompt mode="agent">         ← appended on mode switch
message[2] (system):   <approval_policy policy="suggest">  ← appended on approval change
message[3] (user):     <turn_meta> + user text
  • ensure_mode_prompt_message(mode) — appends <mode_prompt> only when mode changes。仅 mode 变更时追加 <mode_prompt>
  • ensure_approval_prompt_message(mode) — appends <approval_policy> only when approval mode changes。仅审批策略变更时追加 <approval_policy>
  • Both use independent markers and dedup logic; a change to only one triggers only that message。两者使用独立标记和去重逻辑,单独变更仅触发对应消息
  • TUI session resume uses system_prompt_override: true to freeze old message[0] verbatim for upgrade-day cache hits。TUI 会话恢复使用 system_prompt_override: true 冻结旧 message[0],保证升级当天缓存命中

Scope / 改动范围

File / 文件 Change / 改动
prompts.rs Stripped mode_prompt(mode) and approval_prompt_for_mode(mode, …) / 移除模式与审批内嵌文本
engine.rs Removed STABLE_SYSTEM_PROMPT_MODE; added ensure_approval_prompt_message / 移除常量,新增审批追加通道
ui.rs Session resume: system_prompt_override: falsetrue / 会话恢复路径改为冻结旧 prompt
tests.rs +12 tests for marker, message construction, dedup, byte-stability / +12 个测试
capacity_flow.rs Compaction recovery calls both ensures / 压缩恢复调用两条追加通道
turn_loop.rs Same / 同上
ops.rs, session.rs Doc comment updates / 文档注释更新

Testing / 测试

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features (0 warnings)
  • cargo test --workspace --all-features (3955 passed, 0 failed)
  • 12+ unit tests covering marker, text, message construction, dedup, mode-switch, approval-change, byte-stability / 覆盖标记、文本、消息构造、去重、模式切换、审批变更、字节稳定性

Checklist

  • Updated docs or comments as needed / 已更新文档与注释
  • Added or updated tests where relevant / 已添加或更新相关测试
  • Verified TUI behavior manually if UI changes (N/A — no UI changes) / 无 UI 变更
  • No dead code / 无 dead code
  • No trust-boundary changes (auth / sandbox / publishing / branding / prompts) / 未涉及信任边界

Greptile Summary

This PR refactors mode and approval-policy instructions out of the stable message[0] system prompt and into deduplicated append-only system messages, so the base prompt stays byte-identical across mode switches and enables DeepSeek KV prefix-cache hits.

  • prompts.rs: mode_prompt and approval_prompt_for_mode removed from compose_prompt_with_approval_and_model; those strings now travel as tagged <mode_prompt>/<approval_policy> system messages appended by ensure_mode_prompt_message / ensure_approval_prompt_message.
  • engine.rs: refresh_system_prompt made mode-agnostic (always builds Agent baseline); new marker/dedup helpers scan the message list in reverse to avoid duplicate appends; Op::SetModel.mode and Op::SyncSession.system_prompt_override are now effectively dead fields.
  • ui.rs: All SyncSession call sites flip from system_prompt_override: falsetrue, freezing the restored prompt; since the engine gate-checks system_prompt.is_some(), fresh sessions are unaffected, but active sessions that hit workspace-switch or provider-switch paths will also freeze their prompt.

Confidence Score: 3/5

The change is structurally sound, but the ordering of ensure_approval_prompt_message vs. the session.approval_mode assignment in handle_send_message means a user changing their approval policy in Agent mode will have the old policy silently forwarded to the model for that turn.

In handle_send_message, ensure_approval_prompt_message reads self.session.approval_mode before the incoming approval_mode parameter is written to the session (lines 1609-1613). Any in-flight policy change (e.g. Suggest to Never) is therefore invisible to the dedup check: the old policy marker still matches, no new message is appended, and the model operates under the wrong approval contract for the entire turn.

crates/tui/src/core/engine.rs — specifically the handle_send_message call site for ensure_approval_prompt_message relative to the self.session.approval_mode assignment

Important Files Changed

Filename Overview
crates/tui/src/core/engine.rs Core engine: new append-only mode/approval message helpers added, refresh_system_prompt() made mode-agnostic; stale approval_mode read order bug in handle_send_message
crates/tui/src/core/engine/capacity_flow.rs Mode param removed from verify/replan paths; ensure_mode_prompt_message added after targeted context refresh message-list replacement
crates/tui/src/core/engine/turn_loop.rs ensure_mode/approval_prompt_message called after auto-compaction message-list swap; refresh_system_prompt() no longer takes mode
crates/tui/src/tui/ui.rs All SyncSession call sites now use system_prompt_override: true (was false), including workspace/provider switches — freezing prompts beyond just session resumes
crates/tui/src/prompts.rs Mode and approval prompts stripped from compose_prompt_with_approval_and_model; _approval_mode naming misleading but not a bug
crates/tui/src/core/engine/tests.rs +12 unit tests covering marker, message construction, dedup, mode-switch, byte-stability; existing tests updated for no-mode base prompt
crates/tui/src/core/ops.rs Doc comment updated; SetModel.mode field now unused (mode: _); struct unchanged for wire compatibility
crates/tui/src/core/session.rs Doc comment on system_prompt_override clarified to reflect broader persisted/runtime-supplied prefix semantics

Sequence Diagram

sequenceDiagram
    participant UI
    participant Engine
    participant Session

    Note over Engine,Session: Op::ChangeMode

    UI->>Engine: "ChangeMode { mode: Yolo }"
    Engine->>Engine: ensure_mode_prompt_message(Yolo)
    Engine->>Session: add mode_prompt message
    Engine->>Engine: ensure_approval_prompt_message(Yolo)
    Engine->>Session: add approval_policy message
    Engine->>UI: SessionUpdated (message[0] unchanged)

    Note over Engine,Session: handle_send_message approval change bug

    UI->>Engine: "SendMessage { mode: Agent, approval_mode: Never }"
    Engine->>Engine: ensure_approval_prompt_message(Agent)
    Engine->>Engine: "reads self.session.approval_mode = Suggest (stale)"
    Engine->>Engine: latest matches Suggest - skips append
    Engine->>Session: add user message
    Engine->>Session: "self.session.approval_mode = Never (too late)"
    Engine->>Engine: refresh_system_prompt()

    Note over Engine,Session: after compaction

    Engine->>Session: "messages = compacted"
    Engine->>Engine: ensure_mode_prompt_message(mode)
    Engine->>Session: re-append mode prompt
    Engine->>Engine: ensure_approval_prompt_message(mode)
    Engine->>Session: re-append approval prompt
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (5): Last reviewed commit: "feat(cache): append mode prompts without..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Thanks @LeoAlex0 for taking the time to contribute.

This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in .github/APPROVED_CONTRIBUTORS will be closed automatically.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant PR access by commenting /lgtm on a pull request.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors system prompt handling in the TUI engine to improve prefix-cache stability (e.g., for DeepSeek) by delivering mode contracts as append-only system messages rather than inlining them into the initial system prompt. The review feedback highlights a critical bug where the system_prompt_override flag from Op::SyncSession is unconditionally ignored, which prevents synced sessions from refreshing dynamic context when intended. Additionally, the feedback suggests updating a test to properly verify prompt preservation and notes a design issue where using AppMode::Agent as the stable baseline results in duplicate or conflicting instructions for other modes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/tui/src/core/engine.rs Outdated
Comment thread crates/tui/src/core/engine/tests.rs Outdated
Comment thread crates/tui/src/core/engine.rs Outdated
Comment thread crates/tui/src/core/engine.rs Outdated
@LeoAlex0 LeoAlex0 force-pushed the feat/cache-append-only branch from e26e8fd to dd1d16b Compare June 3, 2026 19:06
Comment thread crates/tui/src/core/engine/turn_loop.rs
@LeoAlex0 LeoAlex0 marked this pull request as ready for review June 3, 2026 19:16
@LeoAlex0 LeoAlex0 marked this pull request as draft June 3, 2026 19:20
@LeoAlex0 LeoAlex0 force-pushed the feat/cache-append-only branch from dd1d16b to 941d931 Compare June 3, 2026 19:21
@LeoAlex0 LeoAlex0 marked this pull request as ready for review June 3, 2026 19:30
@LeoAlex0 LeoAlex0 marked this pull request as draft June 3, 2026 19:48
@LeoAlex0 LeoAlex0 force-pushed the feat/cache-append-only branch 6 times, most recently from 832ec40 to 207f670 Compare June 3, 2026 20:33
@LeoAlex0 LeoAlex0 marked this pull request as ready for review June 3, 2026 20:36
@LeoAlex0 LeoAlex0 changed the title feat(cache): append mode prompts without rewriting prefix feat(engine): mode-agnostic system prompt with append-only mode/approval messages Jun 3, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 3, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

Comment thread crates/tui/src/core/engine.rs Outdated
@LeoAlex0 LeoAlex0 force-pushed the feat/cache-append-only branch from 207f670 to 0d280fb Compare June 3, 2026 20:47
@LeoAlex0 LeoAlex0 marked this pull request as draft June 3, 2026 20:52
Move mode declarations from message[0] into append-only system messages so
that message[0] stays byte-stable across mode switches. This lets existing
sessions hit the DeepSeek prefix cache on the very first request after
upgrade — zero-downtime cache migration.

- New functions: mode_prompt_marker, mode_prompt_text,
  mode_prompt_system_message, latest_mode_prompt_matches,
  ensure_mode_prompt_message
- Op::ChangeMode uses ensure_mode_prompt_message instead of
  refresh_system_prompt
- handle_send_message calls ensure_mode_prompt_message before building
  the user message, so the mode declaration always appears ahead of
  user text
- refresh_system_prompt drops its dead _mode parameter
- 12 new tests covering marker/text/message construction, dedup logic,
  mode-switch behavior, and message[0] byte-stability
@LeoAlex0 LeoAlex0 force-pushed the feat/cache-append-only branch from 0d280fb to 37d9525 Compare June 3, 2026 20:52
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.

1 participant