mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 12:59:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - People can save follow-up messages while an agent runs. > - Stop preserves those messages and stops the active execution. > - The queue removed its delivery controls when that execution ended. > - Native admission also refused an explicit resend of older queued input. > - This change adds Send for a stopped queue and records fresh board intent. > - The saved message can continue through normal cleanup and admission checks. ## Linked Issues or Issue Description **What happened?** A person queued a message with a screenshot while an agent was starting. Steering was not yet available. They stopped the run. The message remained visible, but no control could send it. Native queue delivery also rejected this transition. **Expected behavior** A stopped queue offers Send. The click preserves message IDs, content, attachments and original authors. It starts at most one eligible continuation. Stop remains effective until the person gives new direction. **Steps to reproduce** 1. Start a native task and queue a second message. 2. Stop the active run before it consumes that message. 3. Open the task after cleanup. Observe the saved message with no send control. Refs #16965 for the queue-control change and #15015 for stopped conversation recovery. No open repair for this exact transition was found. ## What Changed - Show Send for a saved deferred queue with no active run. - Accept the existing board queue action for a stopped native target. Keep live native interruption restricted. - Use the server-recorded click time to authorize older native messages. Reject stale, malformed and wrong-actor intents. - Reuse normal queue dispatch, revision checks, cleanup proofs and execution admission, including a failed native predecessor after a verified Send. - Ignore a fully committed, successful recovered export when admitting a fresh message, while retaining the wait for results still committing. - Honor an applied `/new` conversation boundary while retaining native execution holds until the existing stop and cleanup checks verify them. Workspace holds also survive reset. Check the newest failed or cancelled native execution even when older recovery actions exist. - Keep native automatic delivery separate from the explicit Send action. Update execution and access documentation. ## Verification - Meaningful regressions failed before the fixes: stopped queues lacked Send, native admission refused fresh intent, cleanup and dispatch receipts were missing, deferred rows disappeared, live plan Interrupt rejected its own receipt, and Agent Chat `/new` inherited an old non-workspace execution hold. - Six assembled-wakeup cases fail before the reset cleanup fix. All 11 reset/history cases pass after it: live processes, missing identity, quarantined cleanup, remote leases and live controllers remain held; verified cleanup proceeds and retires the hold. Two more cases fail before the fallback fix and pass after it: Send verifies a stopped or live native predecessor despite a later reset receipt and no recovery record. - **525 server tests pass** across seven queue admission, route, dispatch, envelope, provenance, blocker and conversation files. **343 queue/thread UI tests** also pass. Server/UI TypeScript, token and map guards pass. The final scoped server typecheck also builds native Runner successfully. - The latest regressions prove that completed recovered exports permit fresh input, and clearing conversation history still requires old native stop and cleanup verification; an unsafe workspace still blocks after `/new`; failed native Send reaches actual continuation dispatch with the original queued comment. A result still being committed remains deferred. History plus a newer Stop cannot hide a missing process/controller/lease receipt. - The real Chromium Stop/Send journey passes on current head `75fa5e7222c59f43ff7ac326e81093bc9da28330`: queue a follow-up, Stop, reload, explicitly Send, then verify exactly one useful response and no live run. Earlier browser journeys cover legacy Interrupt, interruption during a write and Stop followed by a new direction. These use an isolated app/database and controlled ACP fixture, not an external AI account or a staging deployment. - Negative cases retain actor, company, issue, assignee, queue and cancellation-receipt bindings; cleanup/process/workspace/controller holds; approvals; pause and budget gates. Typed responses keep their original resolver and immutable result. - Full local recursive typecheck stopped in unchanged Rust Runner compilation with ENOSPC, so the chained full tests/build did not run. All hosted CI gates pass on final commit `75fa5e7222c59f43ff7ac326e81093bc9da28330`, including typecheck, tests, build and E2E. Apex review on that commit is 5/5, with no unresolved findings. Earlier unrelated CI failures involved PostgreSQL fixture-cleanup deadlock and an unchanged decision-expiry deadline assertion; the latter's 20 cases pass locally without source changes. No assertions or limits were weakened. Local resource cleanup was limited to this task's verified completed test processes and rebuildable npm caches. ## Risks - Send can authorize a new turn for a saved message after Stop. A Send click must be newer than the stopped execution; a live plan Interrupt is bound to the cancellation receipt produced by that same click. It does not imply that cleanup finished or that an external action is safe to replay. - The existing endpoint and intent format are reused. No schema or credential change is required. Live native steering and legacy interruption keep their existing paths. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, repository inspection, tool use and code execution. The session does not expose the exact serving model ID or context window size. ## 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 #` / `Refs #` 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>
194 lines
11 KiB
Markdown
194 lines
11 KiB
Markdown
---
|
|
navigation: {"section":"Tasks and conversations","sectionOrder":20,"order":60}
|
|
---
|
|
|
|
# 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.
|
|
|
|
Feature ID: `steering`.
|
|
|
|
Agent access: [companion](../agent-access-map/steering.md).
|
|
|
|
## Sub-features
|
|
|
|
- `queue`: `steering/queue` — a busy task saves follow-ups and shows their order and wait reason.
|
|
- `edit-order-discard`: `steering/edit-order-discard` — edit full Markdown, reorder, and discard eligible saved
|
|
messages; ownership, revision conflicts, and already-dispatched states matter.
|
|
- `steer`: `steering/steer` — explicit user action sends a queued message into a compatible active
|
|
run; unavailable capabilities and provider rejection remain visible.
|
|
- `interrupt`: `steering/interrupt` — stop the current execution and carry accepted follow-ups into
|
|
the eligible continuation without delivering them twice.
|
|
- `stop-and-pause`: `steering/stop-and-pause` — distinguish stop-in-flight, confirmed stop, task/ancestor
|
|
pause, agent pause, and budget hold. Resume must respect the actual gate.
|
|
- `drafts`: `steering/drafts` — switching tasks, reloading, and uncertain submission retain the
|
|
correct task's draft and attachment receipts.
|
|
- `approval-queue`: `steering/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. After Stop, choose **Send** on a saved queue to explicitly continue it without recreating the messages or attachments. Native steering still requires an active turn; Send after Stop is a separate user decision. 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](./README.md#before-driving-a-journey).
|
|
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](../../ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx)
|
|
covers order, failure restoration, unavailable steering, stale revisions, and
|
|
discard acknowledgements. [Queue routes](../../server/src/__tests__/issue-queued-comments-routes.test.ts)
|
|
cover durable ordering, authorization, steering acknowledgements, interrupt
|
|
recovery, and concurrent dispatch. [Composer](../../ui/src/components/task-chat/TaskChatComposer.test.tsx)
|
|
covers drafts and uncertain saves.
|
|
|
|
[Task-detail delivery races](../../ui/src/pages/IssueDetail.test.tsx) 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.
|
|
A queued turn's own starting input stays in history before execution, including
|
|
board messages that reopen a task. Later inputs keep their queue controls and
|
|
full canonical order. A maintenance hold displays its current admission wait;
|
|
drain release and expiry prompt normal admission without a manual retry.
|
|
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.
|
|
|
|
```sh
|
|
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](../../tests/e2e/composer-stop.spec.ts) with
|
|
[its configuration](../../tests/e2e/playwright-composer-stop.config.ts) and
|
|
[ACP stop/continuation](../../tests/e2e/acp-stop-continuation.spec.ts). Inspect their
|
|
fixture and opt-in prerequisites; skipped native cases are not native proof.
|
|
The explicit [follow-up/Stop/resume journey](../../tests/runner-e2e/followup-stop-resume-flow.ts)
|
|
requires a later user message before continuation. The
|
|
[database Stop accounting regression](../../server/src/services/native-runtime/native-stop-accounting.integration.test.ts)
|
|
checks complete and late receipts without another provider turn. These are
|
|
separate scopes; database success does not establish deployed or Slack acceptance.
|
|
|
|
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.
|
|
|
|
Fixture-backed recovery check: start with an agent in its completed paused
|
|
lifecycle state, then insert a new accepted `deferred_issue_execution` request
|
|
through the [PostgreSQL recovery fixture](../../server/src/modules/wake-queue/adapters/postgres.test.ts).
|
|
Normal wake admission
|
|
rejects paused agents, so this pending state cannot be created by sending a new
|
|
message through the ordinary UI or API. Let release and periodic recovery run.
|
|
Confirm that the saved request remains pending. Resume the agent, wait for its
|
|
lifecycle to become ready, and verify one delivery. An explicit Pause cancels
|
|
older queued work; those cancelled receipts must not be replayed. A paused
|
|
request must not prevent another eligible recipient's queued work from proceeding.
|
|
|
|
### `agent-chat`
|
|
|
|
Automated: [agent chat sessions](../../tests/e2e/agent-chat-sessions.spec.ts) 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](../../server/src/__tests__/question-response-delivery.test.ts)
|
|
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](../../server/src/services/chat-interaction-arbitration.test.ts)
|
|
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](./recovery.md).
|
|
- A healthy idle Agent Chat is waiting for input, not stranded unfinished work.
|
|
|
|
### Storybook verification
|
|
|
|
#### `drafts`
|
|
|
|
Capability ID: `steering/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](../../ui/src/components/task-chat/TaskChatComposer.tsx), [composer-draft.ts](../../ui/src/lib/composer-draft.ts). Tests: [TaskChatComposer.test.tsx](../../ui/src/components/task-chat/TaskChatComposer.test.tsx), [issue-queued-comments-routes.test.ts](../../server/src/__tests__/issue-queued-comments-routes.test.ts)
|
|
|
|
#### `approval-queue`
|
|
|
|
Capability ID: `steering/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](../../ui/src/components/task-chat/TaskChatQueuedMessages.tsx), [question-response-delivery.ts](../../server/src/services/question-response-delivery.ts). Tests: [TaskChatQueuedMessages.test.tsx](../../ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx), [question-response-delivery.test.ts](../../server/src/__tests__/question-response-delivery.test.ts), [issue-queued-comments-routes.test.ts](../../server/src/__tests__/issue-queued-comments-routes.test.ts)
|