Skip to content

feat(queue): add live steer and follow-up session input queues - #1105

Draft
Fodesu wants to merge 5 commits into
felinics:mainfrom
Fodesu:codex/steer-followup-queue-20260827
Draft

feat(queue): add live steer and follow-up session input queues#1105
Fodesu wants to merge 5 commits into
felinics:mainfrom
Fodesu:codex/steer-followup-queue-20260827

Conversation

@Fodesu

@Fodesu Fodesu commented Aug 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Add independent Steer and Follow-up queues for a session's active run, kept in the configured session runtime backend (memory or redis). They are live coordination state, deliberately not part of the PostgreSQL durable model.
  • Steer items are bound to the run active at admission and consumed at the step commit barrier, fenced to the run's owner, generation, and fencing token. A final step that finds no steer seals the run; a terminal run rejects its remaining steers.
  • Follow-up items are bound to the session. At a run's terminal boundary the oldest follow-up is claimed and started through ordinary turn admission, which stays the single owner/fencing authority.
  • A complete turn that meets a busy session on a local channel (web, cli) is parked in the follow-up queue with its full StartTurnCommand; platform channels keep the bounded busy retry because their reply is delivered from the caller's run handle.
  • Remove the legacy /now, /btw, /next channel paths; add /steer and /queue slash commands, REST queue endpoints, split-runtime RPC, and a composer queue panel with edit, reorder, delete, and follow-up-to-steer promotion. /steer works on every channel because it joins the run the channel is already streaming; /queue is accepted only on local channels (web, cli) and platform channels get queue_follow_up_unsupported_channel, since they could not receive the reply of a run the server starts from the queue.

Architecture

  • PostgreSQL remains authoritative for run admission, ownership and fencing, history, and decision state. It stores no queue payloads, claims, or step-commit records; the schema and migration chain are unchanged.
  • Steer and Follow-up are separate Go types, runtime methods, and Redis keys. One queue's API cannot read or mutate the other's items.
  • Each queue holds one document per session, bounded to 64 pending items (queue_capacity_exceeded) and the newest 64 terminal items. Invocation IDs give best-effort replay protection within that retention.
  • Redis queue transactions watch only the queue document and the run key; the session state key is read but not watched, because it is rewritten on every streamed delta.
  • A steer claimed at a deferred-decision step is not taken; the continuation's first committed step claims instead, and an existing unapplied claim is advanced to the run's current owner on reclaim.
  • History keeps its turn model: every user message opens a turn, so an applied steer is persisted as its own turn and the output after it is filed under that turn while the run ID is unchanged. The web projection names the post-steer assistant segment after that turn once it is known.

Design notes: docs/design/session-input-queues.md.

Deliberate deviations from the original spec

  • Queues are transient. A process restart with the memory backend, or a Redis data loss, discards pending items; normal run and history durability is unaffected.
  • Follow-ups are not rejected when the run they were queued behind aborts, fails, or is lost; the queued input still belongs to the session. Only run-bound steers are rejected at terminal.
  • The follow-up handoff is not atomic: an ordinary message may take the session slot between the terminal boundary and the follow-up's admission. The item stays accepted and is claimed again at the next terminal boundary.

Also in this PR

  • Decision continuations (ask_user, tool approval) now log the private cause of a native stream error before it is reduced to a public error code.
  • Web transcript merge inserts runtime frames in projection order instead of re-sorting by timestamp, and orders user before assistant within one turn when timestamps tie.

Validation

  • go build ./..., go vet ./..., golangci-lint run ./..., and go test ./... pass.
  • The Redis live-queue contract test (MEMOH_TEST_REDIS_URL) passes against a local Redis across two backend instances.
  • pnpm lint and the UI contract guard pass; apps/web/src/store Vitest suites pass.
  • pnpm vitest run reports 5 failures in desktop/media-gallery/dockview tests and pnpm -C apps/web typecheck fails in bot-select and config-schema-form; those files are identical to main and fail there in the same environment.

Known issue outside this PR

Answering an ask_user question on deepseek-v4-flash can fail with The reasoning_content in the thinking mode must be passed back to the API. The continuation replays stored history and DeepSeek rejects assistant tool-call messages without reasoning_content. This is independent of the queue changes and is being fixed in Twilight's completions provider.

@Fodesu
Fodesu force-pushed the codex/steer-followup-queue-20260827 branch from 2384cc1 to 8982893 Compare August 29, 2026 19:35
@Fodesu
Fodesu marked this pull request as ready for review August 30, 2026 05:34
@Fodesu
Fodesu requested review from a team as code owners August 30, 2026 05:34
@chen-ran
chen-ran force-pushed the codex/steer-followup-queue-20260827 branch from 619c0ea to 26092c5 Compare August 31, 2026 04:21
@Fodesu
Fodesu marked this pull request as draft September 1, 2026 12:50
@Fodesu
Fodesu force-pushed the codex/steer-followup-queue-20260827 branch 2 times, most recently from d0d7983 to 9179aa6 Compare September 2, 2026 17:41
@Fodesu
Fodesu marked this pull request as ready for review September 2, 2026 18:26
@Fodesu
Fodesu force-pushed the codex/steer-followup-queue-20260827 branch 2 times, most recently from 94d90bf to dd6087c Compare September 3, 2026 12:30
@chen-ran
chen-ran marked this pull request as draft September 4, 2026 18:23
Steer and follow-up queues for a session's active run, kept in the configured
session runtime backend (memory or Redis) rather than PostgreSQL.

- Separate steer and follow-up Go types, runtime methods, and Redis keys, with
  accepted/claimed/applied/canceled lifecycle, reorder, edit, cancel, promote,
  and invocation replay.
- Steer claims are fenced to the active run's owner, generation, and fencing
  token and consumed at the step commit barrier; a final step that finds no
  steer seals the run.
- Follow-ups are claimed at the run's terminal boundary and started through
  ordinary turn admission.
- HTTP queue routes, channel /steer and /queue slash commands, split-runtime
  RPC, web queue panel, and live steer projection in the session runtime.

Squashed from the PostgreSQL-backed iterations of this branch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Fodesu
Fodesu force-pushed the codex/steer-followup-queue-20260827 branch from dd6087c to e3f9bb4 Compare September 7, 2026 10:26
@Fodesu Fodesu changed the title feat: add durable steer and follow-up session queues feat(queue): add live steer and follow-up session input queues Sep 7, 2026
Fodesu and others added 4 commits September 7, 2026 18:55
…urns into follow-ups

- Reject a run's accepted and claimed steers when the run reaches a terminal
  state and seal the run against late admission (CloseSteerRun).
- Skip steer claims on deferred-decision steps; the continuation's first
  committed step claims instead. Advance an existing unapplied claim to the
  run's current owner, generation, and fencing token on reclaim.
- Store busy-session turns in the follow-up queue with the full
  StartTurnCommand so continuations keep channel routing, reply targets, and
  attachments. Remove the separate DeferredTurnBackend. Start follow-ups with
  one in-flight starter per session and kick a start when the enqueue observed
  a run that has already ended.
- Bound each queue to 64 pending items (queue_capacity_exceeded) and compact
  terminal items to the newest 64 after every mutation.
- Watch only the queue document and run key in Redis queue transactions; the
  session state key is rewritten on every streamed delta and is now read but
  not watched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sactions

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
publicAgentStreamEvent replaces a native error event's detail with a stable
code before the event leaves the application, and continueRuntimeDecision
derives its failure cause from that public event. A failed ask_user or tool
approval continuation therefore logged only agent.response_interrupted. Record
the original event text in the continuation loops before the conversion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing by timestamp

applyRuntimeTranscript re-sorted the whole transcript whenever a runtime frame
carried a user turn with turn_position. sortChatMessages falls back to
timestamps when the other side has no position, and a live assistant turn
never has one. Once the first step commit persisted the request user, the
frame replaced it with the stored row whose timestamp is the commit time,
later than the assistant turn that had already started streaming, so the
reply rendered above its own request.

The runtime projection already orders a run's turns (request users, then
assistant segments split around each steer). Insert that block as delivered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Fodesu
Fodesu force-pushed the codex/steer-followup-queue-20260827 branch from e3f9bb4 to af56c8a Compare September 7, 2026 10:56
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