Files
PaperClipAI/doc/agent-commentary.md
T
DottaandPaperclip e5927e1db9 fix: give commentary its own default read permission (#16903)
## Thinking Path

> - Paperclip lets people manage AI agents and inspect their work.
> - Commentary collects agent suggestions and complaints for company
members.
> - Commentary used the permission for detailed agent-action audits.
> - Owners, admins and viewers could therefore receive a permission
error when they opened Commentary.
> - This pull request gives commentary its own permission and enables it
for all human company roles.
> - Members can read feedback while task privacy and detailed audit
access keep their own checks.

## Linked Issues or Issue Description

Refs #16884, which added the Commentary browser. A search found no open
PR for this permission fix.

**What happened?**

Commentary returned `Missing permission: audit:view_agent_actions` for
company members without the detailed audit grant. Default owners, admins
and viewers did not receive that grant.

**Expected behavior**

All human company roles can read commentary by default. A separate
`commentary:view` permission controls this access. Private source tasks
remain private. Commentary access does not grant detailed agent-action
audits or CSV exports.

**Steps to reproduce**

1. Sign in as an active company owner, admin or viewer without
`audit:view_agent_actions`.
2. Open Audit, then Commentary.
3. Observe the permission error before this change.

**Paperclip version or commit**

Base commit: `7c58ea07b2`.

**Deployment mode**

Authenticated server or Cloud tenant.

## What Changed

- Add `commentary:view` to the shared permission contract and all human
role defaults.
- Require that permission for commentary list and detail reads.
- Add an idempotent upgrade migration for existing active human members.
Preserve existing grants and scopes.
- Add the commentary default when approving saved human invitation
grants that omit it, including pre-upgrade invitations. Preserve
explicit commentary scopes.
- Add regression coverage for role defaults, permission removal, audit
separation, private tasks and migration replay.
- Update the API description, specifications, database notes and access
maps.

## Verification

- Passed 68 tests across commentary reads, role grants, old-invitation
HTTP acceptance, migration replay, agent audit routes, access services
and migration snapshot drift under the repository's locked dependencies.
- Confirmed the new role and read tests failed before the
implementation.
- `node scripts/check-maps.mjs` passed.
- Migration numbering and safety checks passed during the build.
- Full `pnpm -r typecheck` and `pnpm build` passed locally after the
invitation fix.
- The final-commit CI matrix passed, including all test shards,
typecheck, build, public-install checks and canary smoke. The redundant
local `pnpm test:run` was stopped after the full CI matrix completed;
the 68 focused local regressions passed separately.
- The first install bypassed the stale lockfile and selected a newer
diff-viewer API. The initial typecheck and build failed in that
unrelated plugin. Installing the locked versions cleared both failures;
the tracked lockfile is unchanged.
- The first locked-dependency regression run hit the 15-second limit
while importing the audit route graph in its first test. Moving that
import into suite setup fixed the timeout; all 58 regressions then
passed.
- Greptile found that pre-upgrade invitations retain old grant lists.
New helper and HTTP regressions reproduced this gap before the fix, then
passed after approval added the missing commentary default.
- Greptile reviewed the final commit at 5/5 with no remaining actionable
findings. Its earlier thread is resolved.
- No UI code changed. The permission tests use real disposable
PostgreSQL databases and HTTP routes.

## Risks

- The upgrade deliberately enables commentary for every existing active
human company member, including viewers. Existing commentary grants
retain their saved scopes.
- Source-task privacy still filters previews, counts, searches and full
messages. Agent-authenticated browsing remains denied.
- Agent-action audit grants, detailed filters and CSV export permissions
keep their current behavior.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository editing and
command execution. The exact runtime model ID and context window are not
exposed to 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
#` / `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-19 16:58:17 -05:00

175 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent complaints and suggestions
Agents submit incidental feedback into the current instance's `agent_commentary`
table. Complaints preserve a raw reaction; suggestions describe an improvement.
Both are free-form, internally attributed records, not anonymous feedback.
## Agent entry points
Skill-capable legacy adapters automatically receive `complain` and
`suggestion-box` alongside the `paperclip` operational skill, including existing
agents with empty optional-skill selections. The shared helper lives in that
operational skill at `scripts/submit-agent-commentary.mjs`. It reads the body
from stdin and uses the existing `PAPERCLIP_API_URL`, `PAPERCLIP_API_KEY`,
`PAPERCLIP_COMPANY_ID`, and `PAPERCLIP_RUN_ID`. The helper uses the existing
Node.js runtime with no additional dependencies; custom runtimes can call HTTP
directly. Sandboxed runs use the callback bridge.
Native runs receive `submit_complaint({body, idempotencyKey})` and
`submit_suggestion({body, idempotencyKey})` in standard, ask, and planning modes.
Specialized completion-review runs retain their restricted tool set. Native
submission uses the bound authority without a shell or API credential.
Submit before finishing the run.
Instructions closely adapt Warp's
[complain](https://github.com/warpdotdev/common-skills/blob/main/.agents/skills/complain/SKILL.md)
and [suggestion-box](https://github.com/warpdotdev/common-skills/blob/main/.agents/skills/suggestion-box/SKILL.md)
skills, with their MIT notices included. Paperclip changes the transport,
attribution, suggestion form, and size limit. Agents submit proactively when
warranted, without routine previews or announcements, then continue working.
Answer truthfully if a user asks about feedback or submitted content. Operators
can exclude these legacy skills through the existing runtime skill policy.
Brevity and at most three suggestions per run are guidance, not server quotas.
Do not report the same incident through both paths or retry failed submissions.
## HTTP contract
`POST /api/companies/:companyId/agent-commentary` accepts an authenticated agent
and its active run (`Authorization: Bearer …`, `X-Paperclip-Run-Id: …`):
```json
{"kind":"suggestion","body":"The tool returned before persistence completed. Wait for the write before returning success.","idempotencyKey":"write-completion-1"}
```
Only these three fields are accepted. Identity and the optional task reference
are server-derived. Board callers cannot impersonate agent feedback. Native runs
use their dedicated tools rather than this legacy HTTP path.
Bodies must contain non-whitespace text and may contain up to 524,288 JavaScript
string code units, matching ordinary issue documents. No truncation is applied.
Existing 10 MiB HTTP and callback bridge request limits remain. Keys are nonempty
and at most 240 characters.
Creation returns HTTP 201 with `{id, kind, createdAt, replayed:false}`. Identical
replay in the same company/run returns HTTP 200 with the same ID and timestamp
and `replayed:true`. Conflicting key reuse returns 409. Invalid input returns 400;
invalid authority returns 401/403; storage failure returns a sanitized 503.
Authorization precedes replay. The helper accepts an optional second argument
for a stable key, generating one otherwise. It makes one request with a
10-second HTTP deadline and exits zero on failure with a content-free diagnostic.
It never follows redirects with credentials or claims an uncertain write succeeded.
## Storage and inspection
Rows contain company, agent, run, nullable issue, kind, body, retry key, payload
hash, and timestamp. Known run secrets and credential syntax are redacted before
storage; instructions remain essential because redaction cannot guarantee
secrecy. Writes and one content-free activity record commit together. Feedback
bodies are excluded from HTTP diagnostics and dedicated mutation receipts.
Browse feedback at Audit → Commentary (`/activity/commentary`). The page has
text search, agent/type filters, 50-message pages and a full-message dialog with
task/run context. Filters, page offsets and selected messages have shareable
URLs (`?entry=<message-id>` opens a message directly).
The board-only read API is `GET /api/companies/:companyId/agent-commentary`
(`agentId`, `kind`, `q`, `limit` up to 100, `offset`) and
`GET /api/companies/:companyId/agent-commentary/:id`. Both require company access
and `commentary:view`. All human company roles, including viewers, receive this
permission by default. The upgrade migration grants it to existing active human
members without changing existing commentary grants. Human invitation approval
adds the default to saved grant lists that omit it, including older invitations,
while preserving an explicit commentary grant's scope. Commentary access does not
grant agent-action audit access, detailed audit filters or audit CSV exports;
those retain `audit:view_agent_actions`. Source-task privacy filters previews, counts and
details. List results contain at most 280 characters
per message; the full body is loaded only on opening a message. Feedback whose
source task was deleted stays hidden, using the task binding retained on its run.
There is no notification, automatic task creation, or external forwarding.
This is separate from first-party Telemetry, OpenTelemetry
Observability, and the run log. Ordinary provider transcripts can still contain
submitted tool arguments; there is no anonymity or ephemeral-storage promise.
Authorized operators inspect the instance database, for example:
```sql
SELECT id, kind, body, agent_id, run_id, issue_id, created_at
FROM agent_commentary
WHERE company_id = '<company UUID>'
ORDER BY created_at DESC;
```
The service enforces company/run ownership. Foreign keys clear the task reference
on task deletion and cascade deletion with its run, agent, or company. Normal
logical database backups include these rows. No retention scheduler or backfill.
## Verification
Focused coverage lives in `server/src/__tests__/agent-commentary.integration.test.ts`,
`agent-commentary-helper.test.ts`, `agent-commentary-skills.test.ts`, the shared validator tests, and the HTTP logger
and sandbox callback bridge suites. These exercise real PostgreSQL and routes,
atomic audit rollback, replay races, authority changes, deletion, default mounts,
Unicode, and the exact document-body boundary.
The opt-in live smoke uses existing Codex login credentials, copied to a private
temporary home, and a disposable instance database. It calls the real legacy
adapter and production native-session executor. It incurs normal provider usage:
```sh
node cli/node_modules/tsx/dist/cli.mjs server/scripts/verify-agent-commentary-live.ts
```
To run the same checks on real Daytona sandboxes, install the standalone
provider's dependencies, provide its API key and an immutable image reference,
and specify a Linux amd64 runner built from this checkout:
```sh
pnpm --dir packages/plugins/sandbox-providers/daytona install --frozen-lockfile
PAPERCLIP_LIVE_ENVIRONMENT=daytona \
PAPERCLIP_LIVE_DAYTONA_ENV_FILE=/path/to/private.env \
PAPERCLIP_LIVE_LINUX_RUNNER=/path/to/linux-amd64/paperclip-runnerd \
PAPERCLIP_LIVE_EVIDENCE_PATH=/tmp/commentary-evidence.json \
node cli/node_modules/tsx/dist/cli.mjs server/scripts/verify-agent-commentary-live.ts
```
The private env file needs only `DAYTONA_API_KEY` and
`PAPERCLIP_E2E_DAYTONA_IMAGE` (`...@sha256:...`). Alternatively, export those
variables directly. A positional `legacy` or `native` argument selects one path.
For a current Linux runner, add an export stage to a temporary copy of the root
Dockerfile and build it with `--platform linux/amd64 --target commentary-runner-export
--output type=local,dest=/tmp/commentary-runner-linux`:
```dockerfile
FROM scratch AS commentary-runner-export
COPY --from=runner-build /app/packages/paperclip-runner/runner/target/release/paperclip-runnerd /paperclip-runnerd
```
The smoke calls the production Daytona provider hooks for creation, execution,
file transfer, private ingress, and confirmed deletion. Legacy feedback crosses
the sandbox queue callback bridge. Native feedback crosses the runner's private
WebSocket and live tool authority. The current Linux runner is staged and
verified independently of the base image. Fixture setup creates authority;
the real agent submits all feedback. This is a focused feedback transport smoke,
not a full Runner E2E catalog qualification or browser onboarding test.
Both paths passed locally and on Daytona on 2026-10-06. Each persisted one complaint and one suggestion
with company, agent, run, and task attribution; each then wrote the continuation
marker. Both exited zero, left task status unchanged, created no task comments,
and recorded two content-free activity entries. The script emits the attributed
row IDs and timestamps as evidence, optionally saves a JSON report, and deletes
its temporary data, credentials, and remote sandboxes. Remote reports are emitted
only after confirmed sandbox deletion.
The Daytona verification used base image
`ghcr.io/paperclipai/paperclip-daytona-runner@sha256:b81a86d5242088f9d832666a411f09da7438d92e99f9962ccf88ebe439cd3b32`
and the Linux runner built from `abf47b5953d14aa7a8dbf2941460b5df8a4d66c3`
(binary SHA-256 `c794141152ae2e2986da0df14b01a54e4448949e06ec4031c83bad8a9fff8bac`).
| Environment | Runner | Complaint row | Suggestion row | Submitted at (UTC) |
| --- | --- | --- | --- | --- |
| Local | Legacy Codex | `59413a00-1de2-4bb1-bcc6-9c4b54c64aa6` | `3db2364d-3e15-4f47-846f-875d3902999d` | 20:35:10 |
| Local | Native Codex | `5da22b5f-41df-4de5-8ba0-d9345ab01267` | `2d5abe17-dd41-403c-a5ee-4729f2d58921` | 20:35:26–20:35:27 |
| Daytona | Legacy Codex | `27c9d0aa-8477-409f-9da0-e8ffa48dee50` | `209681c9-d1e9-4ce1-999e-48fa07692389` | 20:33:07–20:33:09 |
| Daytona | Native Codex | `6eb001bb-4bcf-43f7-8717-f662f53dc7c3` | `77c383d8-a997-49e5-a33e-25c70e15c0b2` | 20:31:54 |