Files
PaperClipAI/maps/agent-access-map/steering.md
T
Devin FoleyandPaperclip c9213d1be4 fix: send saved queued messages after Stop (#17959)
## 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>
2026-10-27 20:34:14 -07:00

213 lines
8.1 KiB
Markdown

# Steering and queued messages — agent access
Product map: [Steering and queued messages](../feature-map/steering.md).
Feature ID: `steering`.
Review date: 2026-10-12. Source commit: `58e16dbddfc9c8d0cf60867f9d9847f0feda055b`.
Native REST transport reconciled in this change; see the
[shared transport contract](README.md#native-rest-transport).
This is a source-reviewed baseline of representative operations and known gaps.
Each capability needs operation-level review before its entire scope can be
qualified. No live agent execution is claimed. Follow the [map contract](../README.md)
for format, permission checks, evidence and maintenance.
## `queue`
Capability ID: `steering/queue`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/issues/{id}/queued-comments`.
Agent access: `partial` — REST / native call_api for visible queue; server-owned queue
admission.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user.
Compact task run reads retain `wakeupRequestId` and `queuedCommentSourceWakeIds`
to identify consumed input queues, including coalesced requests. These receipt
fields follow the existing task/run read checks and grant no queue mutation authority.
The UI can place a queued turn's starting input in history without recording
consumption. Drain release and expiry only reconsider existing queued runs under
normal admission authority. They grant no new agent control or replay authority.
Accepted deferred requests remain pending while their recipient agent is paused.
Resume uses the normal admission checks. This grants no new pause, resume, queue
mutation, or execution authority to an agent.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Agents can read eligible queue state; sending/steering the person's queued
messages requires separate authority.
## `edit-order-discard`
Capability ID: `steering/edit-order-discard`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `PATCH /api/issues/{id}/queued-comments/{commentId}`.
- `PUT /api/issues/{id}/queued-comments/order`.
- `DELETE /api/issues/{id}/queued-comments/{commentId}`.
Agent access: `restricted` — Board-user queue mutation API.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — assert the documented agent denial with an owned test fixture;
verify any listed agent request/result path separately. A delegated operation needs its
own policy and access review.
Gaps: No agent user-message edit/reorder/discard path; explicit delegated-user policy
needed before granting one.
## `steer`
Capability ID: `steering/steer`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `POST /api/issues/{id}/queued-comments/{commentId}/steer`.
Agent access: `restricted` — Board-user execution control API; agent receives eligible
new run/wake.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — assert the documented agent denial with an owned test fixture;
verify any listed agent request/result path separately. A delegated operation needs its
own policy and access review.
Gaps: Steering requires a board user at the HTTP route. Native `call_api`
preserves this denial and cannot impersonate the steering user.
## `interrupt`
Capability ID: `steering/interrupt`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `POST /api/issues/{id}/queued-comments/interrupt`.
- `POST /api/heartbeat-runs/{runId}/cancel`.
Agent access: `restricted` — Board-user interruption/cancel API.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user. The same queue endpoint accepts an explicit **Send** for a stopped native queue. It records fresh board intent, retains original comment authors, and keeps execution cleanup and admission gates. Agent Chat `/new` also retains native process, controller, workspace and lease checks before subsequent messages execute. It does not grant agents the ability to send saved user messages or interrupt live native turns.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — assert the documented agent denial with an owned test fixture;
verify any listed agent request/result path separately. A delegated operation needs its
own policy and access review.
Gaps: Explicit operator action is distinct from an agent yielding/blocking its own turn.
## `stop-and-pause`
Capability ID: `steering/stop-and-pause`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `POST /api/issues/{id}/tree-holds`.
- `POST /api/agents/{id}/pause`.
Agent access: `restricted` — Board control APIs; agent paperclip_finish/paperclip_block
for its own turn.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user. A saved pre-Stop message does not authorize
automatic resumption. A later authorized user message may continue with retained
input after current cleanup, ownership, and budget gates pass.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — assert the documented agent denial with an owned test fixture;
verify any listed agent request/result path separately. A delegated operation needs its
own policy and access review.
Gaps: Task-tree/agent pause and confirmed stop need policy-preserving authority; no
generic agent management path.
## `drafts`
Capability ID: `steering/drafts`.
Review: `baseline`.
API: `not-applicable` — No shared server draft resource for this presentation state;
cross-client drafts would be a deliberate new capability.
Agent access: `not-applicable` — Browser-local task composer draft / submission state.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — use the product recipe for the local/developer surface. Verify
listed shared-resource reads separately; local presentation alone is not agent API
evidence.
Gaps: No shared server draft resource for this presentation state; cross-client drafts
would be a deliberate new capability.
## `approval-queue`
Capability ID: `steering/approval-queue`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/issues/{id}/interactions`.
- `GET /api/issues/{id}/queued-comments`.
Agent access: `partial` — Agent can inspect authorized persisted interactions; server
continuation/queue delivery.
Policy: Queue inspection follows task visibility; queued user-message mutation and run
cancellation require a board actor/user.
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L16166) at
the reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Resolving as a human and controlling queued wakes is separate from seeing the
approval response.