Skip to content

Resume prompt scan from the current turn - #905

Closed
moyotar wants to merge 1 commit into
xenodium:mainfrom
moyotar:chat-incremental-prompt-runs
Closed

moyotar wants to merge 1 commit into
xenodium:mainfrom
moyotar:chat-incremental-prompt-runs

Conversation

@moyotar

@moyotar moyotar commented Oct 23, 2026 •

Copy link
Copy Markdown

agent-shell-prompt-ranges walked every font-lock-face change in the buffer, and chat mode calls it on every shell event, so relabel cost grew with the whole session.

I noticed Emacs stuttering while a fast agent was streaming into a long session. Over 40s of streaming, the profiler put 71% of CPU in agent-shell-chat--relabel-buffer → the prompt scan. That buffer was 1.6 MB: 10 turns, ~97% collapsed tool output, ~29k face runs. Each relabel took 0.17–0.27s.

Cache the prompt runs before the next-to-last one and rescan only from there, since a busy shell streams between the submitted prompt and the live one. An after-change-functions hook drops the cache on any change at or before that run (clear, session reload). If the cached position no longer holds a prompt face, it falls back to a full scan. A narrowed buffer is scanned in full and leaves the cache alone.

Same buffer (1.7 MB by now):

before after
agent-shell-prompt-ranges 190 ms ~1 ms
agent-shell-chat--relabel ~270 ms ~2 ms

Over ~2800 calls in normal use, including turn boundaries, the cached result matched a full scan every time I compared them.

Not addressed:

  • The scan still grows with the current turn, so one turn with huge tool output stays slow.
  • agent-shell-prompt-ranges still checks each run for the end-of-prompt marker (~1 ms here).
  • A change before the cached run made with modification hooks inhibited would leave the cache stale. I found no such change to font-lock-face in agent-shell or shell-maker.

Checklist

  • I agree to communicate (PR description and comments) with the author myself (not AI-generated).
  • I've reviewed all code in PR myself and will vouch for its quality.
  • I've read and followed the Contributing guidelines.
  • I've filed a feature request/discussion for a new feature.
  • I'm making visual changes, so I'm including screenshots so you can view and discuss.
  • I've added tests where applicable.
  • I've updated documentation where necessary.
  • I've run M-x checkdoc and M-x byte-compile-file.

agent-shell-prompt-ranges walked every font-lock-face change in the
buffer, and chat mode calls it on every shell event. On long sessions
this dominated streaming: a 1.6 MB buffer (10 turns, mostly collapsed
tool output) took ~0.2s per relabel.

Cache the prompt runs before the next-to-last one and rescan only from
there, since a busy shell streams between the submitted prompt and the
live one. An after-change-functions hook drops the cache on any change
at or before that run (clear, session reload). Narrowed calls scan
without touching the cache.
@moyotar
moyotar force-pushed the chat-incremental-prompt-runs branch from 77d4fe3 to c125764 Compare October 23, 2026 07:53
@moyotar moyotar changed the title Resume chat prompt scan from the current turn Resume prompt scan from the current turn Oct 23, 2026
xenodium added a commit that referenced this pull request Oct 23, 2026
agent-shell-prompt-ranges stopped at every font-lock-face change,
which grows with the session and slowed chat mode relabeling on long
buffers. Prompts end where comint's output field gives way to input,
as comint-next-prompt relies on, so walk field instead for faster
results.
@xenodium

Copy link
Copy Markdown
Owner

Thanks for the profiling and the PR! Before caching, I'd like to see if we can make the scan itself cheap: a cache depends on every earlier change running modification hooks, which nothing enforces.

I've pushed a change that walks field boundaries (as comint-next-prompt does) instead of every font-lock-face change. It should be much faster than what we have now. Can you give that a try please?

@moyotar

moyotar commented Oct 24, 2026

Copy link
Copy Markdown
Author

Tried 78a2c94: same ranges as the old scan, and the stuttering is gone. Per relabel:

session before 78a2c94
1.2 MB, 22 turns 84 ms 17 ms
439 KB, 2 long turns 24 ms 3.7 ms
396 KB, 6 turns 3.8 ms 0.6 ms

Agreed the cache isn't worth its fragility, closing this.

FWIW the cost still grows with the buffer, ~14 ms per MB on my DeepSeek sessions: next-single-property-change on field still steps through every text property interval. At the ~15 relabels/s I see while streaming, that should only start to feel sluggish past 2–3 MB. If I hit that, I'll look into skipping relabels when the buffer hasn't changed. Thanks!

@moyotar moyotar closed this Oct 24, 2026
@xenodium

Copy link
Copy Markdown
Owner

Thank you for retesting and profiling.

should only start to feel sluggish past 2–3 MB.

Increasing acp-drain-time-budget may help here, but I haven't tried it for these cases.

@moyotar
moyotar deleted the chat-incremental-prompt-runs branch October 26, 2026 01:51
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