Files
PaperClipAI/doc/AGENT-ARTIFACTS.md
T
Devin FoleyandPaperclip 0f84df91da fix(native): accept a completed child's file in the parent's completion report (#20325)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - In onboarding, the agent plans the user's first task, creates one
child task for the work, and waits for it
> - The `first-task` skill tells the agent to publish a requested file
on the child task. When the parent wakes, the agent links the child's
file and does not publish a copy
> - Native `register_deliverable` is bound to its current task, so the
parent cannot register the child's file again
> - The native completion check required each cited attachment to be on
the task being completed
> - So when the parent cited the child's download link,
`paperclip_finish` failed, and the agent reported the finished
onboarding task as blocked
> - This pull request lets a completion report cite an attachment on a
direct child task that is done
> - The benefit is that onboarding closes after its child task delivers
a file, and the user does not see a false "blocked" status

## Linked Issues or Issue Description

Related: #18258 (onboarding artifact completion instructions).

**What happened?**
An onboarding run created a child task to build and publish a project.
The child task registered `project.zip` with `register_deliverable` and
was marked done. The parent woke with reason `issue_children_completed`.
The agent called `paperclip_finish` with `reportedWorkDisposition:
"done"` and cited the child's `/api/attachments/<id>/content` link. The
call failed two times with:

```
Completion cites no registered attachment on this task. Use register_deliverable for the requested file and cite deliverable:<attachmentId> from its receipt.
```

The skill does not let the agent register the file on the parent, so the
agent called `paperclip_block` with "Paperclip completion validation" as
the owner. The parent task stayed `blocked`. Nothing wakes it again.

**Expected behavior**
The parent completes and links the child's file.

**Steps to reproduce**
1. Create a task with a child task.
2. In a native run on the child, register a file with
`register_deliverable`, and mark the child done.
3. In a native run on the parent, call `paperclip_finish` with
`reportedWorkDisposition: "done"` and the child's
`deliverable:<attachmentId>` or
`/api/attachments/<attachmentId>/content` as evidence.
4. The call fails with "Completion cites no registered attachment on
this task".

## What Changed

- `server/src/services/native-runtime/native-deliverable-feedback.ts`:
when a cited attachment is not on the task,
`validateNativeDeliverableEvidence` also looks for it on a direct child
task with status `done`. If it is there, the citation is accepted.
- The child's file is context, the same as an attachment from an earlier
run on the same task. It does not count as this run's publication of a
file that the parent's own request names.
- With `lockSources`, the finalization path locks the attachment and
child task rows with `FOR SHARE NOWAIT`, as it does for other sources.
  - The error text now says "on this task or its completed child tasks".
- This applies to the `paperclip_finish` check and to the two
finalization checks, because they use the same function.
- `doc/AGENT-ARTIFACTS.md`: the native runner section states that a
completion report can cite a file on a completed direct child task.

## Verification

- Extended "prepares one verified same-run attachment and replays
without duplicates" in
`server/src/services/native-runtime/native-runner-file-handoff.test.ts`:
  - An attachment on an unrelated task is still rejected.
  - An attachment on a child task that is not done is still rejected.
- An attachment on a done child task is accepted, through
`paperclip_finish` and through the locked finalization path.
- When the parent's own request names a file, the child's file alone
does not satisfy it.
- Without the source change, the test fails with the same error as the
run above.
- `npx vitest run` on `chat-attachment-reuse`,
`native-completion-feedback`, `native-completion-format-feedback`,
`native-deliverable-feedback`, and `native-runner-file-handoff` tests in
`server/src/services/native-runtime`: 281 passed.
- `npx vitest run src/__tests__/native-result-commit-work.test.ts
src/__tests__/native-status-arbiter-corpus.test.ts` in `server`: 167
passed.
- `npx tsc --noEmit -p server/tsconfig.json` passes.

## Risks

- Only direct children with status `done` are accepted. A grandchild's
file, or a file on a child that is not done, is still rejected.
- A parent that names a requested file in its own request still needs
its own delivery evidence. This keeps the current rule for earlier
outputs.
- A task that is already blocked by this error does not change. The user
can mark it done, or comment so the agent runs again.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (code reading, database queries, shell commands, and
test runs).

## 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
- [ ] All Paperclip CI gates are green
- [ ] 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-11-09 09:44:17 -08:00

13 KiB

Agent Artifact Upload Workflow

Generated files that a board user or reviewer should inspect as deliverables must be attached to the Paperclip issue before the agent chooses a final disposition. A local workspace path is not enough, because cloud users and reviewers often cannot access the agent's disk.

Upload files from a task

People can open the task's Artifacts tab and use the upload icon, or drop files anywhere in the panel, including its header and existing artifact cards. Uploads save directly to the task. They do not send a message, assign an agent, or release a pause hold. Files from people and agents appear together in Artifacts.

Markdown imports create task documents. Repeated filenames get a new document key instead of replacing an existing revision. Hidden system keys and artifact-review prefixes are reserved; imports with those filenames receive a visible upload document key. Other supported files use the existing attachment API and its type and size limits. Failed uploads show an inline Retry action while successful files remain available.

Native runner

When register_deliverable is available, use it for files in the bound local or remote workspace. Supply a workspace-relative contentRef, basename filename, contentType, exact byteSize and SHA-256, title, and a stable idempotencyKey. The tool verifies the file, stores an attachment and artifact work product, and binds it to the response. Generic API tools and a legacy API key are unnecessary.

register_deliverable stores the file on its current task. A completion report can also cite a file on a direct child task that is done, because the child's own completion verified that file. A parent that delegated a file to a child links the child's file and does not register a copy. The child's file does not count as delivery of a file that the parent's own request names.

Named JSON file outputs and explicit publication requests require the same verified delivery evidence as other file outputs. A saved summary document does not replace a requested downloadable JSON file. Reading JSON inputs or discussing their contents inline does not by itself require a file attachment. JSON output intent is recognized from a direct filename or an explicit output destination. Ambiguous descriptions of input files joined by to remain text requests; use as, into, or an explicit output file when an artifact is required. A Deliverable: or Deliverables label also states the output. A file named on that line, or in the list directly below it (blank lines between items are allowed), is a requested file. This includes relative paths such as dist/index.html and web page files such as index.html and styles.css. Items that describe a repository change, pull request, commit, or branch do not count, nor do files named as inputs (a comparison of input.csv and baseline.csv), files the item forbids (do not create report.pdf), files to be edited in place (update styles.css) unless the item also asks to publish them, files to be written inline in the reply (index.html and styles.css inline in the reply), or items that say no files are created. An inline or no-upload instruction about another output in the same item does not cancel a named file (index.html and a short summary in the reply).

The completion gate permits a narrow sentence-bound exception for one requested file when the following sentence explicitly qualifies it, for example: Write memory.txt. This is personal memory, not a task deliverable. or Write internal-proof.txt. This is an internal verification file, not a deliverable. Each qualifier applies to one file in the immediately preceding request sentence. Separate file requests can each qualify when each has its own following qualifier. Multiple files requested in one sentence, unqualified report requests, and explicit attachment/download requests still need verified accessible publication evidence. Saying only “for internal verification” does not establish this exception. Expected denied native writes in a negative test are separately recognized as denied attempts, not requested deliverables.

Wait for the receipt. It includes attachmentId, contentPath, and downloadPath, along with the existing command, revision, entity references, and disposition. Reuse the original key after an ambiguous result. A receipt confirms storage and response binding in Paperclip; it does not confirm delivery to an external chat provider. If registration fails, use the returned error to resolve the failure or explain the limitation; do not describe a workspace path as an uploaded file.

With workspace-relative file access, create or copy task deliverables into the directory returned by get_workspace.cwd before registration. AGENT_HOME holds personal instructions and memory. If registration reports a missing workspace file, copy the file to that task directory and retry with a relative contentRef.

Legacy adapters

Use Bash to run the helper bundled with the Paperclip skill from the repo root; installed skill files may not retain executable permissions:

bash skills/paperclip/scripts/paperclip-upload-artifact.sh path/to/output.webm \
  --title "Walkthrough render" \
  --summary "Rendered walkthrough for review"

The helper uses the authenticated Paperclip API from the current heartbeat environment:

  • PAPERCLIP_API_URL
  • PAPERCLIP_API_KEY
  • PAPERCLIP_COMPANY_ID
  • PAPERCLIP_TASK_ID
  • PAPERCLIP_RUN_ID

It uploads the file to POST /api/companies/{companyId}/issues/{issueId}/attachments and creates an artifact work product on POST /api/issues/{issueId}/work-products by default. The command prints issue-safe markdown links for the final task comment.

Task artifact presentation

Existing and newly arriving agent attachments, work products, and documents add the task's Artifacts tab without selecting it, opening the side panel or mobile drawer, or changing the current document/file link. If the pane is closed, the tab is available when the user opens it. This uses stored object IDs, so it works with either runner. Uploading a file and registering its work product counts as one arrival. Revisions and repeated query refreshes preserve dismissed tabs and the user's selection. Plans retain their existing Plan-tab behavior; unregistered user input attachments remain in the conversation.

Uploaded Artifacts vs Workspace Files

Use uploaded artifacts for deliverables: videos, PDFs, screenshots, archives, reports, rendered HTML, or any file the board should inspect without needing the agent's checkout. Attachment-backed artifact work products set type to artifact and provider to paperclip, with metadata canonicalized from the uploaded attachmentId.

Use workspace_file metadata only for important files that intentionally remain in a project or execution workspace, such as source files, committed markdown plans, or generated files whose meaning depends on the checkout. Workspace-only references are useful signposts, but they are not durable uploads.

Expected work product metadata shape:

{
  "resourceRef": {
    "kind": "workspace_file",
    "issueId": "<issue-id>",
    "workspaceKind": "execution_workspace",
    "workspaceId": "<execution-workspace-id>",
    "relativePath": "doc/plans/example.md",
    "line": 1,
    "column": 1,
    "displayPath": "doc/plans/example.md:1:1"
  }
}

workspaceKind is execution_workspace or project_workspace. line and column are optional. relativePath must be relative to that workspace root; do not store host-local absolute paths as workspace references.

Workspace file links resolve only inside registered Paperclip workspaces. The default target is the current issue's execution workspace first, then its project workspace. A link may target another same-company project workspace only when it carries both that projectId and workspaceId. Paperclip does not resolve arbitrary machine-wide filesystem paths, absolute host paths, home paths, or relative paths that escape the selected workspace.

Completion Pattern

When a task produces a user-inspectable deliverable file:

  1. Generate and verify the file locally.
  2. Upload it with skills/paperclip/scripts/paperclip-upload-artifact.sh.
  3. Keep the artifact work product unless the file is incidental; pass --no-work-product only for supporting files that should not be promoted.
  4. Link the printed attachment URL in the final issue comment.
  5. Then set the final issue status.

For a response that is explicitly intended for an external chat conversation, also pass each intended file with paperclipai issue comment --attachment-id <id>. Paperclip binds only those exact uploaded files to that comment; other task attachments remain internal.

Final comments should name and link the uploaded artifact or work product, not just the local filesystem path. For workspace-only files, include the work product title and recorded relative path. Local paths can be included as diagnostic context, but they cannot be the only access path. Browse/search is a fallback for recovering workspace files when the issue link or chip is not available, not the preferred way to deliver files to users.

Video Examples

Upload an .mp4 render:

bash skills/paperclip/scripts/paperclip-upload-artifact.sh dist/demo.mp4 \
  --title "Demo video render" \
  --summary "MP4 render for board review"

Upload a .webm render:

bash skills/paperclip/scripts/paperclip-upload-artifact.sh out/walkthrough.webm \
  --title "Walkthrough video" \
  --summary "WebM walkthrough render"

The helper detects .mp4, .webm, and .mov content types. If a renderer uses an unusual extension, pass the MIME type explicitly:

bash skills/paperclip/scripts/paperclip-upload-artifact.sh render.bin \
  --title "Demo video render" \
  --content-type video/mp4

Direct API Pattern

If the helper is unavailable, use the same API shape:

curl -sS -X POST \
  "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues/$PAPERCLIP_TASK_ID/attachments" \
  -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
  -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
  -F 'file=@"dist/demo.mp4";type=video/mp4'

Then create a work product when the uploaded file is the deliverable:

curl -sS -X POST \
  "$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/work-products" \
  -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
  -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
  -H "Content-Type: application/json" \
  --data-binary @artifact-work-product.json

Use type: "artifact", provider: "paperclip", and metadata containing the uploaded attachmentId. The server canonicalizes contentType, byteSize, contentPath, openPath, downloadPath, and originalFilename.

The optional executionWorkspaceId on work-product create and update requests must identify an execution workspace in the same company. A project workspace ID is a different identifier and cannot be used here. Omit the field when no execution workspace is available, or send null to clear an existing link. Invalid references return 422 without changing the work product or the current primary product.

Verification

The file-delivery integration suite runs the real helper through queue and HTTP/2 gateways against a disposable API, database, and storage. It also tests native registration with generic API tools disabled, duplicate retries, Unicode filenames, company isolation, and downloads after deleting the workspace.

pnpm exec vitest run server/src/__tests__/file-delivery-bridges.test.ts

To run the same suite on disposable Daytona sandboxes, install the standalone Daytona plugin's dependencies and set DAYTONA_API_KEY in the test process:

PAPERCLIP_FILE_DELIVERY_DAYTONA=1 pnpm exec vitest run server/src/__tests__/file-delivery-bridges.test.ts

The live fixture deletes each sandbox before checking that its attachments remain downloadable from Paperclip. It does not run unless explicitly enabled.

Text attachment previews

In the task chat layout, select a text attachment in Artifacts, a work-product card, or a chat attachment chip to open a named right-side tab. Reopening the same attachment focuses its existing tab. Tabs can be switched and closed. On mobile, the same viewer opens in the task details drawer.

Markdown work products keep their expandable review document, annotations, revision indicator, and document links. Open in tab is a separate action. Work-product cards keep Download as a direct original-file download. Text file cards provide Open in tab beside their existing actions. CSV cards keep this action before and after loading their data preview.

Markdown attachments offer Rendered and Raw views. Other supported text files display literal text. Image references and diagram source remain inert; opening a preview does not load attachment-selected media URLs. The viewer provides a download action. Preview reads are limited to 512 KiB; oversized, unsupported, or unavailable files show an explicit fallback instead of attempting an unbounded render. A failed read can be retried. Workspace files continue to use the existing workspace file viewer.

Storybook: Tasks / Text file tabs covers opening from Artifacts, Markdown, plain text, empty files, oversized files, missing attachments, and a narrow panel.