Skip to content

feat(fork): acks, an Edited mark and a divider in display modes - #149

Merged
orbitingflea merged 1 commit into
mainfrom
fork/display-ack-edit-cite
Nov 14, 2026
Merged

orbitingflea merged 1 commit into
mainfrom
fork/display-ack-edit-cite

Conversation

@orbitingflea

@orbitingflea orbitingflea commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

This pull request adds acks, an Edited mark and a divider to the Messages only, Messages + Dashboard and Chat + Replies display modes.

What behaviour changes

Behaviour change: yes, users of these three modes see acks, dividers and Edited marks.

  • An ack is a short "on it" note. For each user message, the agent may send one ack, then one answer, then nothing more. A rewrite of an ack makes it the answer.
  • A thin rule parts two agent messages in a row.
  • A message the agent rewrote shows a small "Edited" mark. Hover over it for the edit time.
  • In Chat + Replies, an edited reply counts as new, so the Replies list shows "1 new".
  • A citation made in the Replies view already goes to that view's reply box, so it is unchanged.

If a citation made in the Replies view still lands in the main box, could the fork's owner say what they did?

Why

A long task left the user with no word until the answer. A rewrite left no trace, so the user could miss it. With acks, two agent messages often sit in a row, and the rule shows where one ends.

Checks

Unit tests cover the ack rules, edit times, unread counts and dividers. The new cases failed before the change. The repo's checks for changed files pass. In the web app, at desktop and phone size, a live agent sent acks, answers and rewrites, and its extra sends were refused. An edit of an answer already read showed "1 new".

How to undo

Revert the commit. Stored acks then show as ordinary messages. Nothing is stored outside the thread's history.

Design notes
  • send_to_user takes an optional ack (true, false or null) of at most 200 bytes. In Chat + Replies, each user message keeps its own count, in the main list and in every sub-thread, as before.
  • replyStage.ts keeps one stage per user message: open, acked or answered. The stage only moves forward. judgeSend and judgeReplace decide every send and replace, on the Messages path and in each sub-thread. Each refusal says which rule it broke.
  • replace_send_to_user has no ack field and always leaves an answer. After a replace of an ack, no further answer goes out.
  • A replace is refused while the answer it would rewrite is still being delivered. Otherwise it could match its old text against the ack and then overwrite the answer. A review of the design found this case.
  • In the Messages modes, an ack before any typed user message is refused. In Chat + Replies, an ack needs reply_to, so an ack with new_sub_thread is refused. A message with only an ack stays in the refusals' list of unanswered messages, and an accepted ack starts no summary run.
  • In Normal, an ack gets only the size check. The size is JSON-escaped UTF-8, counted as for the 1000-byte send limit.
  • The rule is drawn within one view, either the main list or one sub-thread. A rewrite to the same text adds no Edited mark. An edited reply shows "1 new" on its reply line too, and the edit becomes the sub-thread's latest activity.
  • The fold records ack on an ack and editedBy on a rewritten message. The web takes the edit time from when the rewrite completed. An edited first message in the main list gets only the mark, since the main list has no unread state. A reply edited before this change, in a sub-thread read before that edit, shows as new once.
  • The replace_send_to_user description now says the agent answers each user message once, after at most one ack, and that a replace makes an ack the answer. Mode notices are unchanged. The tool list grows from 52134 to 52373 bytes.
  • The mark sits under the text, at the left. An inline "(edited)" and a mark in the right corner were also built. Markdown messages can end in a code block or a table, and a corner mark floats far from short text on a wide screen.
  • No repo eval covers the new tool text, because the tool-use eval does not load these tools.
  • No database or contract change. The history stays the only record. The README's display-mode line is updated.
  • Upstream files: apps/web/src/types.ts +1 (ChatMessage.editedAt), MessagesTimeline.logic.ts +3 −1 (followsAgentMessage and its isRowUnchanged line), TimelineAssistantRows.tsx +4 (two imports, two JSX lines) and agentContextBudget.json +1 −1. All other logic is in fork files.
  • Mobile shows no display mode, so it shows no rule or mark. With a provider whose calls do not name themselves (Codex, ACP, OpenCode), a call stored more than 10 seconds late can still let a second answer through, as with plain sends today.

In Messages only, Messages + Dashboard and Chat + Replies, an agent could
send exactly one message per typed message, a rewrite left no trace, and
an edited reply did not count as new.

- send_to_user takes an optional ack (at most 200 bytes): per user
  message the agent may send one ack, then one answer. A send after the
  answer is refused, and replace_send_to_user always leaves an answer,
  so it can turn the ack into the answer. One stage table per user
  message (replyStage.ts) decides sends and replaces for both the
  Messages path and each sub-thread, and a replace waits while the
  answer it would rewrite is still being delivered.
- The history fold records which rewrite changed a message. The web
  shows a small "Edited" mark under it, and in Chat + Replies an edited
  reply takes the edit's time, so it shows as new and moves the
  sub-thread's latest activity.
- A thin rule parts two agent messages drawn in a row in a view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@orbitingflea orbitingflea added the agent-context-increase Accepts growth of the text agents receive label Nov 14, 2026
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:XL labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.5 KiB 21.5 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 1 1 0 (0.0%) 8 ✅

Other scenarios

All 64 scenario measurements match main.

All 64 scenario measurements
Provider Scenario Metric Main baseline This PR Impact PR ceiling
Codex short turn resume WebSocket decoded 10.5 KiB 10.5 KiB 0 B (0.0%) 12.7 KiB ✅
Codex short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn running, cold open HTTP decoded 281.7 KiB 281.7 KiB 0 B (0.0%) 302.7 KiB ✅
Codex long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Codex long turn running, history page HTTP decoded 92.4 KiB 92.4 KiB 0 B (0.0%) 107.4 KiB ✅
Codex long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Codex long turn finished, cold open HTTP decoded 381.5 KiB 381.5 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, socket cold open WebSocket decoded 381.6 KiB 381.6 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, work page HTTP decoded 186.2 KiB 186.2 KiB 0 B (0.0%) 200.2 KiB ✅
Codex long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Codex idle thread resume WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell snapshot HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Codex fleet shell subscribe WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Codex fleet shell resume check rows 0 0 0 0 ✅
Codex stale shell reload HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex stale shell reload rows 27 27 0 (0.0%) 27 ✅
Codex stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Codex stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex stale todo list cold open HTTP decoded 72.4 KiB 72.4 KiB 0 B (0.0%) 83.0 KiB ✅
Codex stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Codex stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅
Claude short turn resume WebSocket decoded 10.6 KiB 10.6 KiB 0 B (0.0%) 12.7 KiB ✅
Claude short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn running, cold open HTTP decoded 283.3 KiB 283.3 KiB 0 B (0.0%) 302.7 KiB ✅
Claude long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Claude long turn running, history page HTTP decoded 93.4 KiB 93.4 KiB 0 B (0.0%) 107.4 KiB ✅
Claude long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Claude long turn finished, cold open HTTP decoded 384.2 KiB 384.2 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, socket cold open WebSocket decoded 384.3 KiB 384.3 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, work page HTTP decoded 188.1 KiB 188.1 KiB 0 B (0.0%) 200.2 KiB ✅
Claude long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Claude idle thread resume WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell snapshot HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Claude fleet shell subscribe WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Claude fleet shell resume check rows 0 0 0 0 ✅
Claude stale shell reload HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude stale shell reload rows 27 27 0 (0.0%) 27 ✅
Claude stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Claude stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude stale todo list cold open HTTP decoded 73.4 KiB 73.4 KiB 0 B (0.0%) 83.0 KiB ✅
Claude stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Claude stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅

Baseline: c460138 · PR result: 14dfa41 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@orbitingflea
orbitingflea merged commit 5c920a4 into main Nov 14, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-context-increase Accepts growth of the text agents receive size:XL vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant