How Work Products and Artifacts Are Stored and Managed in Paperclip

Paperclip stores every tangible deliverable as a first-class record in PostgreSQL, exposing it through a REST API that separates storage, business logic, and HTTP access.

Work products and artifacts in the paperclipai/paperclip repository represent the core mechanism for tracking what a task actually produced — whether that's a PDF report, a video walkthrough, a generated code bundle, or an internal planning document. Understanding how these are stored and managed is essential for integrating with the platform's review workflows.

Database Model for Work Products

The foundation of work product storage is the issue_work_products table, defined in [packages/db/src/schema/issue_work_products.ts](https://github.com/paperclipai/paperclip/blob/master/packages/db/src/schema/issue_work_products.ts). This schema uses Drizzle ORM to map relational data with comprehensive foreign key relationships.

The table structure includes:

  • Relationship fields: id, company_id, project_id, issue_id, execution_workspace_id, runtime_service_id, created_by_run_id
  • Descriptive fields: type, provider, external_id, title, url, status, review_state, is_primary, health_status, summary, metadata, source_trust

These columns enable the UI to render work products, power review workflows, and calculate trust scores based on provenance.

Service Layer: CRUD Operations with Primary Product Handling

All database interactions flow through [server/src/services/work-products.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/work-products.ts). The workProductService encapsulates four core operations:

  • listForIssue(issueId) — Returns products ordered by primacy (primary first) and recency
  • createForIssue(issueId, companyId, data) — Atomically clears any existing primary of the same type before insertion, enforcing "last primary wins"
  • update(id, patch) — Optionally promotes a product to primary within the same atomic transaction
  • remove(id) — Deletes the specified work product

The primary product mechanism uses the is_primary boolean to surface the most important artifact per (issue, type) pair. The service guarantees atomicity so UI consumers never see duplicate primaries.

REST API Endpoints

The HTTP layer mirrors the service functions exactly. Routes in [server/src/routes/issues.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/routes/issues.ts) (lines 6797-7785) expose:

Method Endpoint Service Function Purpose
GET /api/issues/:id/work-products listForIssue List all products for an issue
POST /api/issues/:id/work-products createForIssue Create validated via createIssueWorkProductSchema
PATCH /api/work-products/:id update Modify metadata or toggle isPrimary
DELETE /api/work-products/:id remove Remove a product

The OpenAPI specification in [server/src/routes/openapi.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/routes/openapi.ts) (lines 2271-2304) documents these contracts for client generation.

The Artifact Upload Workflow

When a task produces a binary file requiring board inspection, Paperclip requires a strict two-step sequence documented in [skills/paperclip/references/artifacts.md](https://github.com/paperclipai/paperclip/blob/master/skills/paperclip/references/artifacts.md):

  1. Upload bytes — POST /api/companies/{companyId}/issues/{issueId}/attachments stores the file
  2. Create work product — POST to /api/issues/{id}/work-products with type: "artifact" and metadata.attachmentId referencing the upload
  3. Annotate — Set title, summary, status, reviewState for review context
  4. Close task — Only after both steps succeed; comments alone don't constitute delivery

The helper script [skills/paperclip/scripts/paperclip-upload-artifact.sh](https://github.com/paperclipai/paperclip/blob/master/skills/paperclip/scripts/paperclip-upload-artifact.sh) automates this flow for agent implementations.

Workspace-Only Files: Direct Resource References

Not all deliverables require upload. For files that remain inside the execution checkout — such as generated Markdown plans — set metadata.resourceRef.kind to "workspace_file":

{
  "type": "document",
  "provider": "workspace",
  "title": "Regression test plan",
  "status": "ready_for_review",
  "reviewState": "needs_board_review",
  "metadata": {
    "resourceRef": {
      "kind": "workspace_file",
      "issueId": "<issue-id>",
      "workspaceKind": "execution_workspace",
      "workspaceId": "<workspace-id>",
      "relativePath": "doc/plans/regression-test-plan.md",
      "line": 1,
      "displayPath": "doc/plans/regression-test-plan.md"
    }
  }
}

This pattern lets the board open files directly without blob storage overhead. POST this JSON to /api/issues/:id/work-products using the same authentication headers.

Code Examples: Creating Work Products

npx paperclipai issue work-product:create $ISSUE_ID \
  --payload-json '{
    "type":"artifact",
    "provider":"paperclip",
    "title":"Design mockup",
    "status":"ready_for_review",
    "reviewState":"needs_board_review",
    "isPrimary":true,
    "metadata":{"attachmentId":"<uploaded-attachment-id>"}
  }'

Raw cURL: Upload Then Register


# Step 1: Upload the file

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=@"walkthrough.webm";type=video/webm'

# Step 2: Extract attachmentId from response, then register work product

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 '{
    "type":"artifact",
    "provider":"paperclip",
    "title":"Walkthrough render",
    "status":"ready_for_review",
    "reviewState":"needs_board_review",
    "isPrimary":true,
    "metadata":{"attachmentId":"abc123"}
  }'

Summary

  • Storage: PostgreSQL table issue_work_products with comprehensive metadata and foreign keys
  • Logic: workProductService in server/src/services/work-products.ts handles CRUD and primary product atomicity
  • API: REST endpoints in server/src/routes/issues.ts mirror service operations
  • Binary artifacts: Two-step workflow — upload to attachments endpoint, then create work product with metadata.attachmentId
  • Workspace files: Use metadata.resourceRef.kind: "workspace_file" for direct file references without upload
  • Primary products: is_primary flag with atomic "last wins" semantics per (issue, type) pair

Frequently Asked Questions

What distinguishes a work product from a regular attachment?

An attachment is raw blob storage; a work product is first-class metadata that registers a deliverable for review. According to the Paperclip source code, you must create a work product of type "artifact" referencing the attachment ID before the board recognizes delivery as complete. Attachments alone don't appear in review workflows.

How does Paperclip prevent multiple primary products of the same type?

The createForIssue method in server/src/services/work-products.ts executes an atomic transaction: it first updates any existing primary of the same type to is_primary = false, then inserts the new row with is_primary = true. This "last primary wins" rule guarantees at most one primary per (issue, type) pair without race conditions.

Can I update a work product to make it primary after creation?

Yes. The update service method accepts a patch that may include isPrimary: true. The implementation atomically clears competing primaries of the same type before promoting the specified product, maintaining the same consistency guarantees as creation-time assignment.

What authentication headers are required for work product API calls?

All endpoints require Authorization: Bearer <api_key>. For operations running inside a task execution, include X-Paperclip-Run-Id: <run_id> to attribute the action to the correct run context. The CLI and helper scripts handle header injection automatically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →