Skip to content

fix: preserve queued prompt boundaries end to end - #63298

Open
yingliang-zhang wants to merge 13 commits into
NousResearch:mainfrom
yingliang-zhang:fix/tui-queued-message-boundaries
Open

fix: preserve queued prompt boundaries end to end#63298
yingliang-zhang wants to merge 13 commits into
NousResearch:mainfrom
yingliang-zhang:fix/tui-queued-message-boundaries

Conversation

@yingliang-zhang

Copy link
Copy Markdown
Contributor

Fixes #45560.

Summary

  • replace the busy-session single string slot with an ordered FIFO that keeps each submitted prompt, transport, timestamp, and stable source ID separate
  • persist each source prompt as its own user row so Desktop hydration can restore distinct bubbles
  • make explicit Desktop queue IDs idempotent across inflight, queued, history, and timeout/resume persistence states
  • preserve adjacent user messages as distinct canonical objects; merge only the transient provider copy for strict role alternation
  • add an explicit wire-only [Next user message] marker so the model still sees the semantic boundary
  • strip transcript-only timestamp/message-ID metadata at the common API-copy boundary and in Chat Completions transport defense-in-depth

Root cause

tui_gateway._enqueue_prompt stored one pending prompt. A second busy-time submission replaced that source boundary with prev + "\n\n" + text, so queueing, SessionDB, and Desktop hydration could never recover two independently submitted messages.

A second destructive merge existed in repair_message_sequence: consecutive canonical user dicts were collapsed in place before API construction. That also changed the DB flush cursor and erased source identity even when the gateway had preserved it.

This PR keeps source messages separate through the canonical queue/history/DB path. Provider-only role repair runs on copied API messages, where adjacent users are merged with an explicit boundary marker. No synthetic assistant bubble is added to canonical history.

Exactly-once behavior

Desktop reuses the queue entry ID when a timed-out submission resumes and retries. The gateway now acknowledges an explicit ID already present in the inflight turn, FIFO, canonical history, or SessionDB instead of interrupting/enqueuing it again. JSON-RPC request IDs are deliberately not used for deduplication because clients may reuse them after reconnect.

Verification

  • Python focused matrix covering canonical/wire repair, FIFO integration, SessionDB persistence, idempotency, cursor repair, transport metadata, timestamps, and gateway group paths — 265 passed
  • complete tests/test_tui_gateway_server.py with standalone-TUI environment — 316 passed
  • final tests/test_tui_gateway_queue_on_busy.py16 passed
  • Desktop composer queue + prompt actions Vitest — 57 passed
  • Desktop npm run typecheck and focused ESLint — passed
  • focused Ruff and git diff --check — passed

The six warnings in the 265-test run are existing pkg_resources/datetime deprecations from lark_oapi during Telegram configuration tests.

Relation to #46459

#46459 preserves chronology by writing [Later user message] into text that is still canonically merged. This PR is an independent end-to-end fix for the uncovered tui_gateway single-slot/DB/Desktop path and uses a different architecture: canonical objects remain separate, while a separately implemented [Next user message] marker exists only in the provider wire copy. No code from #46459 is included.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/gateway Gateway runner, session dispatch, delivery comp/cron Cron scheduler and job management comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 12, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: competes with #46459 (earlier, narrower — role-alternation merge only) for the same #45560. This PR is the broader end-to-end superset (FIFO queue + Desktop hydration + exactly-once IDs + cron/gateway mirror). Not a duplicate; maintainer picks the canonical approach.

@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Follow-up from independent review: commit f735371a2 removes the JSON-RPC request-id fallback for message_id, so only explicit stable client source IDs are persisted to platform_message_id. This avoids polluting SessionDB with reconnect-reused RPC sequence numbers. Verified with tests/test_tui_gateway_queue_on_busy.py (16 passed), full tests/test_tui_gateway_server.py (316 passed), Ruff, and git diff --check.

@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Rebased onto latest origin/main and addressed the independent reconnect review findings in commits aa707f6f9 and 84b6afcbf. The final fix migrates each dead queued transport independently while preserving live per-item routing, and makes disconnect detachment a history_lock compare-and-swap so it cannot overwrite a newer retry/resume binding. Deterministic mixed-queue and disconnect/retry barrier tests were added. Final independent hard review: ACCEPT (no P0–P2). Verification: queue/reconnect 21 passed; current core 413 passed; DB/summary 793 passed; Desktop 61 passed + typecheck; ruff/pycompile/diff-check passed.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing the loss of prompt identity through the TUI queue, canonical history, SessionDB, and provider copy. The underlying issue is real: tui_gateway/server.py:5104-5121 merges a second busy prompt into one slot, and agent/agent_runtime_helpers.py:525-555 merges adjacent canonical user messages.

Problems

  • The changed _handle_busy_submit hunk removes the configured steer behavior and treats it as queueing. Current tui_gateway/server.py:5141-5145 calls agent.steer(text) and returns steered; tests/test_tui_gateway_queue_on_busy.py:77-96 covers accepted steering and fallback queueing. hermes_cli/config.py:1792-1796 documents that busy_input_mode="steer" performs mid-turn steering. The PR's replacement test instead asserts steer is never called.

Suggested changes

  • Preserve the accepted steer branch. Apply stable source IDs and the FIFO only when the input is actually queued, including the existing rejected-steer fallback.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-massive Sweeper blast radius: massive — everyone, every turn (invariant surface) labels Jul 16, 2026
@alt-glitch alt-glitch added needs-decision Awaiting maintainer decision before any implementation and removed sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 16, 2026
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Addressed the busy_input_mode="steer" regression in 7a146e180d190bdb4d46ba148edbc97ed9956474.

The busy-submit path again attempts agent.steer(text) and returns steered without queueing when accepted. Rejected or unavailable steering falls back to the PR's identity-preserving queue, retaining submitted_at and message_id; rejected steering also preserves current-main interrupt behavior before queueing.

Verification:

  • Queue/protocol/WebSocket matrix: 110 passed
  • Adjacent session_steer tests: 3 passed
  • uvx ruff check: PASS
  • py_compile: PASS
  • git diff --check: PASS

@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch from 7a146e1 to ae8afd7 Compare July 17, 2026 00:55
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Rebased the reviewed series onto current main and force-pushed exact-lease head ae8afd7f650a2b241e6794da401eae8a4c6c146c. Conflict resolution preserves current-main compute-host/session-routing APIs plus queued submitted_at/message_id, reconnect rehoming, provider-wire boundaries, and accepted/rejected busy_input_mode=steer semantics. Added current-main fixture coverage for routed stored-session resume and compute-host fail-open source metadata. Verification: Python adjacent matrix 676 passed, Desktop queue tests 52 passed, Desktop typecheck PASS, ruff/py_compile/diff-check PASS; range-diff reviewed.

@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch from ae8afd7 to 850ccfa Compare July 17, 2026 01:18
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Main advanced again across the Desktop queue/session-switch surface, so I completed a second exact-lease rebase. Final head is 850ccfa18031bf13bfa87323b16681ceec1e8306 atop 36bf3c2673e39a7b237b04c5a637ff29e1278e66. The semantic conflict resolution preserves current-main sessionId/storedSessionId queue targeting and shared SubmitTextOptions, while retaining #63298 messageId/submittedAt identity. Verification: 1,655 Python tests passed, 54 Desktop queue tests passed, Node 22 typecheck PASS, ruff/py_compile/diff-check PASS; final worktree clean and range-diff reviewed.

@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Fixed the new current-main CI contract failure in 1652df3133db87b0b0893e8310a394ec3d438c1a.

The restore-repair test introduced by ee659d1d8 still expected adjacent canonical user rows to merge. #63298 intentionally preserves those source turns in canonical history/SessionDB and merges only a copied provider payload. The updated regression now proves both sides of that contract:

  • live restore with repair_alternation=True keeps the two user rows and their ordered contents separate;
  • the transient provider copy merges them with the exact [Next user message] marker;
  • canonical input remains unchanged;
  • malformed assistant/assistant restore repair remains covered.

I also corrected the directly stale restore comments/docstrings; production control flow is unchanged.

Verification:

  • affected four-file canonical matrix: 77 passed, 0 failed
  • focused Ruff: PASS
  • py_compile: PASS
  • git diff --check: PASS
  • fresh independent review: ACCEPT, no blockers
  • push parity: local = fork = PR head 1652df3133db87b0b0893e8310a394ec3d438c1a

@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch 2 times, most recently from 27b4340 to 3f5034e Compare August 12, 2026 05:51
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch from 3f5034e to e1130c7 Compare August 12, 2026 08:06
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch 3 times, most recently from caaccf0 to c706e9c Compare August 17, 2026 00:54
@yingliang-zhang
yingliang-zhang requested a review from a team August 17, 2026 00:54
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-queued-message-boundaries branch 2 times, most recently from ef7fe8e to f2c8b04 Compare August 18, 2026 01:05
@alt-glitch alt-glitch added P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins and removed P2 Medium — degraded but workaround exists labels Aug 18, 2026
yingliang-zhang and others added 13 commits August 20, 2026 15:35
Address maintainer review by retaining accepted live steering and identity-preserving queued fallback, including current-main interrupt behavior after rejected steering.
Update rebased fixtures for routed stored-session helpers and assert source metadata survives compute-host fail-open dispatch.
Update the current-main restore-repair tests and explanatory comments for NousResearch#63298's canonical source-boundary contract. Adjacent user turns remain distinct in canonical history while the copied provider wire payload merges them with an explicit marker.
Co-authored-by: Hermes Agent (orchestrator) <noreply@hermes-agent.local>
…loads

Co-authored-by: Hermes Agent (orchestrator) <noreply@hermes-agent.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cron Cron scheduler and job management comp/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:blast-massive Sweeper blast radius: massive — everyone, every turn (invariant surface) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

repair_message_sequence aggressively merges separate user messages (v0.16.0+)

4 participants