diff --git a/packages/paperclip-runner/spec/paperclip-native-runner-spike-spec.md b/packages/paperclip-runner/spec/paperclip-native-runner-spike-spec.md index 7f72558cfa8..61383b8affa 100644 --- a/packages/paperclip-runner/spec/paperclip-native-runner-spike-spec.md +++ b/packages/paperclip-runner/spec/paperclip-native-runner-spike-spec.md @@ -2383,6 +2383,84 @@ The tools are implemented by the runner or harness client and translated into ty `paperclip.ask({ blocking: true })` means that the current turn cannot continue without an answer. It creates a durable attention request; it does not by itself mean that the whole issue has a valid first-class blocker. +### 17.4 Current issue-thread interaction inventory + +This subsection records the current implementation contract as of 2026-08-07. It is an inventory, not the proposed native-runner API. The five kinds below are the complete `IssueThreadInteractionKind` union. They are durable, company- and issue-scoped thread records; they are not provider-native elicitation packets and are not direct issue-status mutations. + +Every interaction row stores its kind and status, requested and effective resolver policy, continuation policy, optional idempotency key, optional source comment/run and addressed agent, title/summary, typed payload/result, creator/resolver attribution, and creation/update/resolution timestamps. The shared statuses are `pending`, `accepted`, `rejected`, `answered`, `cancelled`, `expired`, and `failed`, although each kind uses only the applicable subset. + +#### 17.4.1 Kind matrix + +| Kind | Purpose and request payload | Resolver operation | Durable result | Direct side effects | +| --- | --- | --- | --- | --- | +| `suggest_tasks` | Propose 1–50 issue drafts. Each draft has a stable `clientKey`, title, optional `parentClientKey` or `parentId`, description, priority, work mode, one assignee, project/goal/billing data, labels, and preview visibility. `defaultParentId` supplies the fallback parent. Client keys must be unique; parent-client references must be acyclic and selected descendants require their selected ancestors. | `POST .../accept` accepts all drafts by default or a non-empty `selectedClientKeys` subset. `POST .../reject` may include a reason. Default resolver is board-only. | Accept: status `accepted`, `createdTasks[]` mapping each client key to the new issue id/identifier/title/parent, plus `skippedClientKeys[]`. Reject: status `rejected` and `rejectionReason`. | Acceptance creates real `todo` child issues transactionally in parent-before-child order, inherits source project/goal defaults, writes normal issue/activity records, and queues assignment wakes for newly created assigned issues. It does not advance source workspace state. Rejection only resolves/touches/audits the interaction. | +| `ask_user_questions` | Ask 1–10 structured questions. Each has a stable id, prompt, optional help text, single/multi selection mode, required flag, and 1–10 options; responses can include option ids and `otherText`. The payload may set a card title, submit label, and comment-supersession behavior. | `POST .../respond` validates and normalizes answers. A board user may `POST .../cancel` with a reason. Default resolver is board-or-agents, subject to addressee, same-creator, same-run, company governance, and current-run checks. | Response: status `answered`, `answers[]`, and optional `summaryMarkdown`. Cancel: status `cancelled`, empty answers, `cancelled: true`, and `cancellationReason`. Comment supersession: status `expired`, empty answers, `expirationReason: "superseded_by_comment"`, and `commentId`. | Resolution touches the issue, writes activity/telemetry, and may queue the assignee continuation. It does not create issues or comments. | +| `request_confirmation` | Ask one accept/reject question with labels, optional details, optional/required decline reason, and optional target. An `issue_document` target binds the request to a specific document revision; a `custom` target is descriptive metadata. Server-owned `toolAction` enrichment is a special gateway-generated use and cannot be supplied through the public create route. | `POST .../accept` or `POST .../reject`; rejection enforces `rejectRequiresReason`. Default resolver is board-only. A policy-eligible agent may resolve an explicitly bound issue-review verdict, but cannot resolve its own/same-run request; tool-action confirmations are always board-only. | Status `accepted` or `rejected`; result has `outcome`, optional reason/comment, supersession/stale-target metadata, optional resume-failure metadata, and for tool actions their execution lifecycle/result. | Acceptance first requires the source run's execution-workspace finalization when applicable. A human acceptance can return an `in_review`/user-held request to its creator agent as `todo`; accepted plan confirmation forces a fresh-session workspace refresh; tool-action acceptance delegates approve-and-run to the tool gateway. All resolutions are audited and can queue continuation. | +| `request_checkbox_confirmation` | Ask the resolver to choose a subset of 1–200 known options, then accept or reject the decision as a whole. The payload carries stable option ids/labels/descriptions, defaults, `minSelected`/`maxSelected`, labels/reason policy, details, supersession behavior, and optional target. Empty acceptance is valid when `minSelected` is zero. | `POST .../accept` with optional `selectedOptionIds`, otherwise defaults are used; `POST .../reject` may or must carry a reason. Default resolver is board-only, with the same explicitly bound review-verdict exception as confirmation. | Same confirmation outcome/result contract, with `selectedOptionIds[]` on acceptance. | Same workspace-finalization, creator-return, audit, and continuation behavior as `request_confirmation`; it does not itself perform an action on selected ids. The resumed wake path additionally derives selected option labels/descriptions. | +| `request_item_verdicts` | Ask for an independent verdict on each of 1–200 items. Items have stable ids, labels, optional descriptions/previews/links/attachments. Enabled verdicts must include `approve` and `reject`; `defer` is optional. The payload controls which verdicts require reasons, bulk approve UI, details, supersession behavior, and optional target. | `POST .../verdicts` submits one or more `{id, verdict, reason}` values. Only a concrete user actor can submit. Previously resolved ids are immutable/idempotent; unresolved ids may be submitted later. Default/effective behavior is board-only. The generic accept/reject routes do not apply. | Status remains `pending` while partial, then becomes `answered`. Result is `{outcome: "resolved", complete, items[]}`; each item stores verdict, optional reason, resolver user id, resolution time, and optional comment id. Stale/superseded/administrative outcomes preserve prior item results. | Each non-empty newly resolved batch touches/audits the issue and can wake the assignee; wakes are coalesced in two-second idempotency buckets. No domain action is performed from a verdict by this service. | + +#### 17.4.2 Shared lifecycle matrix + +| Concern | Current authoritative behavior | +| --- | --- | +| Creation and idempotency | `POST /api/issues/:issueId/interactions` validates the discriminated union, requires a current agent run for agent creation, denies low-trust control-plane creation, rejects terminal issues, checks company ownership of source references/addressees, and inserts `pending`. An idempotency key is unique within company + issue: an actor-identical, payload-identical retry returns the existing row; different reuse conflicts. Creating an addressed interaction wakes that eligible addressee with `interaction_pending`. | +| Resolver authority | Defaults are board-only for every kind except `ask_user_questions`, which defaults to board-or-agents. Company governance can select/cap requested policy; the row stores both requested and effective policy. Agent resolution requires a run id, honors an addressed agent, and forbids the creator agent and source run. Item verdict submission additionally requires a user even if policy were configured otherwise. | +| Continuation | Defaults are `wake_assignee` for `suggest_tasks`, `ask_user_questions`, checkbox confirmation, and item verdicts; plain confirmation defaults to `none`. `wake_assignee` resumes after accepted/rejected/answered/cancelled resolution and after each non-empty partial item-verdict batch even while that card remains `pending`. `wake_assignee_on_accept` resumes only for status `accepted`. `none` does not resume. Closed or unassigned issues do not wake. Normal expiry does not wake through continuation policy; if resolution consumes the only valid `in_review` path, a separate review-path-recovery wake may be queued. | +| Target/revision binding | Only confirmation, checkbox confirmation, and item verdicts accept `target`. For `type: "issue_document"`, creation atomically requires the current same-company document revision; resolution rechecks it; publishing/deleting the bound document revision expires the pending card as `stale_target` while recording the prior target. A `custom` target is stored and rendered but has no server-side freshness resolver. Suggested tasks and questions have no target/revision field. | +| User-comment supersession | Questions, confirmation, checkbox confirmation, and item verdicts normalize `supersedeOnUserComment` to `true`. A genuine later human comment (not a run-attributed comment) expires the pending interaction as `superseded_by_comment`; list-time catch-up applies the same rule to historical comments. `suggest_tasks` is not comment-supersedable. | +| Newer-request supersession | Creating a newer `request_confirmation` by the same agent on the same issue expires that agent's older pending `request_confirmation` rows as `superseded_by_newer_request`. This automatic replacement does not apply to questions, checkbox confirmations, item verdicts, or suggestions. | +| Withdrawal, closure, and addressee deletion | The creator agent, current assignee agent, or board user can withdraw any pending kind through `/withdraw`, producing status `cancelled` and outcome `withdrawn`. Moving the issue to `done`/`cancelled` expires every pending kind with outcome `issue_closed` and no closed-issue wake. Deleting an addressed agent cancels its pending cards with `addressee_deleted`. There is no generic time-to-live on an ordinary interaction; tool-action requests have their own expiry. | +| Audit and live UI | Creation and every resolution path write `issue.thread_interaction_*` activity and resolution telemetry and touch the issue. The board UI invalidates the issue-interactions query for every live activity action with that prefix. Its thread reducer orders the returned interaction records into the issue conversation, and one card component renders all five kinds and their pending/history states. | + +#### 17.4.3 Resume data delivered today + +The control plane queues a continuation with durable `interactionId`, `interactionKind`, `interactionStatus`, `sourceCommentId`, and `sourceRunId` in the heartbeat context snapshot. It adds specialized data for: + +- plan confirmation: target revision, accepted target, and confirmation result, plus fresh-session/workspace-refresh routing on acceptance; +- checkbox confirmation: prompt, selected ids, and resolved option labels/descriptions; +- tool-action confirmation: execution state/result; +- item verdicts: newly resolved item ids and the two-second coalescing window. + +The interaction row remains the authoritative full response and is retrievable through `GET /api/issues/:issueId/interactions`. Current legacy wake-envelope normalization exposes only interaction kind/status and checkbox selection generally; it does not provide a uniform resolved interaction object to the model. Therefore an agent must refetch the interaction to obtain question answers, suggested-task materialization, ordinary confirmation reasons, or complete item verdicts. The item-verdict ids placed in the heartbeat context snapshot are also dropped by the current adapter wake-payload normalizer. + +#### 17.4.4 Surface coverage and discovered contract mismatches + +| Surface | Current coverage | Mismatch relevant to a native semantic interaction tool | +| --- | --- | --- | +| REST/server | Lists and creates all five kinds. Accept handles suggestions and both confirmation kinds; reject handles suggestions and both confirmation kinds; respond handles questions; verdicts handles item verdicts; withdraw handles every pending kind; cancel is the board-only questions cancellation route. | Route vocabulary is intentionally kind-specific, so a runner bridge needs a typed operation-to-kind mapping rather than assuming every kind supports accept/reject. | +| CLI | Generic JSON creation validates all five kinds; list, accept, reject, cancel, and respond commands wrap the corresponding REST routes. | No `interaction:verdicts` command exists, so the CLI cannot resolve `request_item_verdicts`. No `interaction:withdraw` command exists; the command named `interaction:cancel` only cancels questions. | +| MCP server | Dedicated create tools exist for suggestions, questions, confirmation, and checkbox confirmation. | There is no create tool for `request_item_verdicts`, no interaction list/resolution/withdraw tools, and the four create schemas omit shared `resolverPolicy` and `addresseeAgentId`. MCP is therefore not a complete exposure of the shared interaction union. | +| Board UI | API wrappers and the issue-thread card support all five request/response shapes, including partial item verdicts; activity-driven live updates invalidate the interaction query. | The attention-queue inline quick action is specialized to plain confirmation; other kinds rely on their full thread card. This is a presentation difference, not a lifecycle mismatch. | +| Heartbeat/adapter resume | The server stores interaction identity/status and the special plan, checkbox, tool-action, and newly-resolved-item contexts before wake. | The skillless/legacy task envelope is not a lossless round trip for all five results: it omits the interaction id and full result, drops item-verdict delta data during adapter normalization, and supplies no inline answers or created-task references. A native runner contract must deliver the durable resolved response explicitly or require a typed fetch owned by the runner, not by the model. | +| Existing docs/instructions | Shared types and runtime support five kinds. | `doc/SPEC-implementation.md` section 10.11 still names only `suggest_tasks`, `ask_user_questions`, and `request_confirmation`. The adapter heartbeat instruction string likewise tells agents to create only those three. The Paperclip skill's headline table covers all five, but its key-endpoint summary omits `/verdicts` and the board-only `/cancel`. | + +#### 17.4.5 Adjacent control-plane mechanisms that are not interaction kinds + +| Mechanism | Why it is separate | Model-facing semantic-tool boundary | +| --- | --- | --- | +| Formal approvals | Company-scoped `approvals` records have approval types, governance status, linked issues, and board decision routes. They can govern hiring, strategy, budget, or an arbitrary board action and are not one response belonging only to an issue thread. | The model may report that governed approval is needed, but the native interaction primitive must not forge, resolve, or collapse a formal approval into `request_confirmation`. Paperclip policy creates and authorizes the approval. | +| Execution-stage decisions | `executionPolicy`/`executionState` route an issue through typed review/approval participants. A participant records approve/request-changes by a legal issue update, and the server advances or returns the stage. | These are finalization/workflow-policy decisions owned by the status arbiter and execution policy. They are not free-form interaction requests and must not be emitted as one of the five kinds merely to bypass a stage. | +| Issue comments | Comments are durable conversation/progress records with authorship, presentation, metadata, wake, reopen/resume, and live-update behavior. A human comment can supersede pending interaction kinds, but is not itself a typed resolution. | Steering/progress may persist as comments through the host. The semantic interaction tool should create a typed card when a typed response is required, not ask the model to parse a later comment as its result. | +| Document annotations | Annotations are anchored discussions on document content/revisions with their own routes, deltas, resolution state, and live query. They discuss a location; they do not bind a yes/no or selection response to the whole target revision. | The runner may surface annotation deltas as task context, but annotation creation/resolution is not one of the five model-facing interaction choices. A revision-wide plan decision remains target-bound `request_confirmation`. | +| Standalone decisions/decision bundles | Decisions encode cross-issue effects and bundles, with separate option/effect authorization and staleness. The routing rule is same issue -> interaction; other issues/bundles -> decision. | The proposed issue-thread semantic tool is scoped to the current issue and cannot express or execute cross-issue effect bundles. | +| Runtime permission requests | Provider/harness permission or elicitation requests gate a tool/process inside one run and have provider/run lifecycle semantics. | They stay runner/runtime events. Even when a tool gateway renders a server-owned `request_confirmation`, the model cannot forge its trusted `toolAction` payload or treat runtime permission as board governance. | + +#### 17.4.6 Authoritative source trace + +- Shared union, statuses, policies, and limits: `packages/shared/src/constants.ts`. +- Payload/result types: `packages/shared/src/types/issue.ts`. +- Create and resolver validators: `packages/shared/src/validators/issue.ts`. +- Durable row and idempotency index: `packages/db/src/schema/issue_thread_interactions.ts`. +- Policy, creation, resolution, materialization, stale-target, supersession, withdrawal, and terminal expiry: `server/src/services/issue-thread-interactions.ts`. +- REST authorization, activity, wake construction, and per-kind routes: `server/src/routes/issues.ts`. +- Continuation retry/context projection: `server/src/services/heartbeat.ts`. +- Legacy wake-envelope normalization/rendering: `packages/adapter-utils/src/server-utils.ts`. +- CLI exposure: `cli/src/commands/client/issue.ts`. +- MCP exposure: `packages/mcp-server/src/tools.ts`. +- Board API and five-kind cards: `ui/src/api/issues.ts` and `ui/src/components/IssueThreadInteractionCard.tsx`. +- Thread projection and live invalidation: `ui/src/lib/issue-chat-messages.ts`, `ui/src/lib/issue-thread-interactions.ts`, and `ui/src/context/LiveUpdatesProvider.tsx`. +- Adjacent approval, execution-stage, comment, annotation, and decision contracts: `server/src/routes/approvals.ts`, `server/src/routes/issues.ts`, `server/src/services/document-annotations.ts`, and `server/src/routes/decisions.ts`. + --- ## 18. Structured result and terminal semantics