## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Task conversations keep follow-up messages pending while an agent works. > - The screen combines optimistic sends, queue snapshots, and run input records. > - A late send response can restore a local queue flag after a run takes the message. > - A stale queue snapshot can then keep the same message in pending. > - This pull request matches the input record to its source queue and clears stale pending flags. > - The benefit is that consumed messages leave pending while unsent messages stay visible. ## Linked Issues or Issue Description **What happened?** A follow-up can remain in the task composer's pending row after a run takes it as input. Local queue state can outlive the send request. The old projection cleared that state for steering, but not for ordinary run-start input. A stale queue read can also retain the consumed entry. **Expected behavior** A message that a started run takes as input appears once in history and leaves pending. Other pending messages remain available. A message assigned to a run that has not started remains pending. **Steps to reproduce** 1. Send a follow-up while a task has a live run. 2. Let a started run record that message as input before or after the send response arrives. 3. Let a queue read still include the consumed message. 4. Observe the message remain pending on the base revision. **Paperclip version or commit** Reproduced against master at `33d24e48ad` with controlled request ordering. **Deployment mode** Source checkout. Browser verification used the production task screen with a simulated native runner and delayed API responses. It did not run a live model or change a deployed instance. Related: [PR #15030](https://github.com/paperclipai/paperclip/pull/15030) addresses acknowledgement matching in the runner console. This change addresses the task-detail composer and does not modify that console. ## What Changed - Clear local pending flags when a started run records the comment as input. - Use live-run input IDs while the run-history query catches up. - Match stale queue entries by their primary or coalesced source wake ID, even when the snapshot targets the previous run or has no target. - Retain coalesced source queue IDs in run context and include receipt IDs in compact task run reads. - Keep unstarted run inputs and authoritative retries pending. Cover send ordering, same-run and successor delivery, queued snapshots, coalesced inputs, and retry consumption. - Add the delivery-race checks to the steering feature map. ## Verification - Before the fix, the first four request-order regressions failed with the consumed message still in pending. Coverage now includes 16 combinations of receipt source, send-response order, and stale snapshot kind. - Focused UI checks: 5 files and 233 tests passed. These cover task detail, queue projection, optimistic comments, queued-message controls, and the task-chat adapter. - `pnpm -r typecheck` passed. - Server receipt checks: 5 files and 139 tests passed with `PAPERCLIP_TEST_POSTGRES_TEMPLATE=0`. These exercise real database reads, coalesced heartbeat input, and polling routes. The default cached-template path timed out during setup on this host; the fresh-cluster path passed without longer timeouts. - Token gates and `node scripts/check-maps.mjs` passed. - Browser: sent a follow-up with a delayed response in the production task screen. The consumed message moved into history. A second unsent follow-up stayed queued. Reload preserved both states. The API and runner were simulated. - `pnpm build` passed. - The full local repository test run was started, then stopped to isolate database setup timeouts. All 55 GitHub CI checks passed on `4ec6820cf1f89880b801fd5f938d72f54591c5e2`, including the full test matrix. One serialized server shard passed on its single rerun after a 15-second route-test timeout; the same 97-test route suite also passed locally. - Greptile reviewed the final head at 5/5. All review threads are resolved. ## Risks - Queue display depends on matching comment IDs, source queue IDs, and a recorded start time. Rows without start evidence stay pending. Newer queues remain visible until their own input receipt arrives. - Run context gains additive receipt metadata. No schema migration is required. Dispatch order, stored queue state, company access, and steering authority stay unchanged. - Browser evidence covers controlled response ordering. It does not establish deployment or live-provider behavior. ## Model Used - OpenAI Codex, GPT-6, with reasoning, tool use, and code execution. The exact serving model ID and context-window limit are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
9.3 KiB
navigation
| navigation | ||||||
|---|---|---|---|---|---|---|
|
Steering and queued messages
People can send follow-ups while an agent works, manage saved messages that have not been consumed, and choose when to steer or interrupt. Stopping a run and pausing a task are separate controls. The UI must preserve the message, explain why it is waiting, and only claim delivery after an authoritative acknowledgement.
Agent access: companion.
Sub-features
queue: a busy task saves follow-ups and shows their order and wait reason.edit-order-discard: edit full Markdown, reorder, and discard eligible saved messages; ownership, revision conflicts, and already-dispatched states matter.steer: explicit user action sends a queued message into a compatible active run; unavailable capabilities and provider rejection remain visible.interrupt: stop the current execution and carry accepted follow-ups into the eligible continuation without delivering them twice.stop-and-pause: distinguish stop-in-flight, confirmed stop, task/ancestor pause, agent pause, and budget hold. Resume must respect the actual gate.drafts: switching tasks, reloading, and uncertain submission retain the correct task's draft and attachment receipts.approval-queue: answered questions and accepted approvals have durable delivery distinct from editable ordinary comments.
How to get to it (user POV)
task-composer
Open an assigned task (/issues/:issueId). Start work, then send follow-ups while
the agent runs. Use the queued-message controls to edit, reorder, discard,
steer, or interrupt when offered. Use the composer stop/pause control separately.
Both task presentations need attention when changing their shared behavior.
agent-chat
Enable Agent Chat and open an agent conversation. Send another message during
a reply and inspect the shared queue. /new starts fresh provider context in
the same conversation; it is an ordered boundary, not a normal model prompt.
request-response
Answer an earlier question or accept a confirmation while successor work is active on the task. Inspect the resulting queued response and its available steer/interrupt control instead of assuming acceptance interrupted execution.
external-channel
Send a follow-up from a linked external chat conversation while its task is running. Use that provider's supported stop/control affordance where available; inspect the resulting task and run in Paperclip.
Driving it
Preconditions: follow the baseline. Use an agent that stays busy long enough to inspect the queue. Record the adapter, native/legacy mode, and advertised steering capability. Test supported and unsupported steering without treating an absent capability as a product failure.
task-composer
Automated: queued-message UI covers order, failure restoration, unavailable steering, stale revisions, and discard acknowledgements. Queue routes cover durable ordering, authorization, steering acknowledgements, interrupt recovery, and concurrent dispatch. Composer covers drafts and uncertain saves.
Task-detail delivery races cover a consumed message whose send response and queue snapshot arrive late. A started run's input receipt removes that message from pending, while a separate unsent message stays queued. Admission to a run that has not started is not delivery. A later retry of the same message remains visible in its new authoritative queue. Receipts retain the primary wake and coalesced source queue IDs. They clear a late snapshot even when its target names the preceding run or is null.
pnpm exec vitest run ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx server/src/__tests__/issue-queued-comments-routes.test.ts
Automated stop coverage is separate: composer stop with its configuration and ACP stop/continuation. Inspect their fixture and opt-in prerequisites; skipped native cases are not native proof.
Manual: queue three distinguishable messages. Edit one, reorder them, reload, and confirm the saved order. Discard one and confirm it never reaches execution. After a queued message enters a started run, confirm it leaves pending even if the send response arrives later. Reload and confirm it appears once in history. Steer one while busy and verify the agent receives that exact message once. Repeat with a rejected steer and a stale second-tab edit; the UI must preserve retryable content and explain the conflict. On a fresh run, interrupt with a queued follow-up and verify confirmed stop followed by one continuation. Pause the task (and separately its ancestor), preserve a draft, then resume the actual hold; no hidden send should occur while paused. Check both task presentations when available; the named component tests do not prove every host integration.
agent-chat
Automated: agent chat sessions covers persistent conversation/session behavior with deterministic harness fixtures; shared composer tests cover the controls. These do not qualify live steering for every harness.
Manual: queue two follow-ups while the conversation replies. Reload, edit or
discard one, then let the other run. Verify one reply and unchanged conversation
identity. Send /new, then a new prompt; old history remains visible, and the
new turn uses fresh provider context. Verify a useful answer rather than only
the session-divider UI. Repeat the queue operations with a live capable harness
when the change touches delivery.
request-response
Automated: response delivery proves queueing/coalescing and retry-safe receipts. The queue-route suite tests separate comment and approval queues, explicit steering, and fresh-session restrictions on plan approvals.
Manual: answer an old question during a new run, confirm that it is saved and queued, and let the active run end. Verify exactly one continuation with the answer. Repeat an eligible approval with explicit steer; another approval that requires a fresh session must not offer same-turn delivery. Do not edit an approval outcome through an ordinary queued-message editor.
external-channel
Automated: chat interaction arbitration covers backend arbitration with simulated requests. It is not a provider UI test.
Manual: send two distinguishable messages through one disposable linked conversation while busy. Inspect their saved task order, run delivery, and external reply. Check a duplicate provider event and unauthorized sender. Provider-specific control semantics and real transport timing remain a manual gap; do not assume browser queue controls are available in every channel.
Gotchas
- Paperclip's queue is server-owned once saved; it is not Omnigent's unsent browser draft buffer. A stale edit must not overwrite a consumed message.
- Steer availability depends on the active runtime's capability, not its brand name. A UI row disappearing optimistically does not prove provider receipt.
- Interrupt, stop, task pause, agent pause, and budget pause have different effects. A running indicator disappearing does not prove remote work stopped.
- An interrupted run may need verified cleanup before a saved follow-up can start. Preserve and inspect the wait reason; see recovery.
- A healthy idle Agent Chat is waiting for input, not stranded unfinished work.
Storybook verification
drafts
Scope: ui Storybook: Task-keyed restored draft and attachment receipts; task switch/reload, failed send and uncertain-save recovery. Production: Retain correct task draft across navigation and reload; inspect authoritative saved receipt before claiming delivery; do not clear storage on every harness mount. Sources: Source: TaskChatComposer.tsx, composer-draft.ts. Tests: TaskChatComposer.test.tsx, issue-queued-comments-routes.test.ts
approval-queue
Scope: ui Storybook: Answered question/accepted approval delivery waiting, eligible steer/interrupt, failed delivery and fresh-session-only approval. Production: Keep response delivery durable and distinct from editable comments; coalesce once, enforce fresh-session restrictions and deduplicate concurrent/retried continuation. Sources: Source: TaskChatQueuedMessages.tsx, question-response-delivery.ts. Tests: TaskChatQueuedMessages.test.tsx, question-response-delivery.test.ts, issue-queued-comments-routes.test.ts