## Thinking Path > - Paperclip manages AI agents and their work. > - Cases keep facts, documents, and one primary task across agent handoffs. > - Each case currently needs its own procedure text. > - Agents need to reuse procedures and move cases between them without replacing the task. > - This change adds company workflow definitions with a name and Markdown prompt. > - Existing handoff and document transactions preserve authority, revisions, and recovery. > - Cases remain behind `experimental.enableCases`. ## Linked Issues or Issue Description Refs #16959. Related proposal: #3951 uses task graphs and a visual editor. This change extends existing Cases with prompt definitions and keeps its current handoff model. **Problem or motivation** Agents cannot share a reusable procedure across cases or inspect a case's movement between procedures. **Proposed solution** Add name-and-prompt workflow definitions. Adopt a prompt into the primary task's revisioned workflow document through a handoff. Keep phases as free text. **Alternatives considered** Defer graphs, workflow versions, run entities, role schemas, authoring screens, and Spec Kit integration. **Roadmap alignment** This extends the existing Cases experiment and same-task review flow. It adds no work queue or new execution engine. The maintainer approved this scope in the implementation plan. ## What Changed - Add company-scoped definition storage, shared request schemas, REST routes, and four optional Cases tools. - Accept a workflow ID at kickoff. Agent handoffs can adopt, refresh, preserve, or detach a workflow. - Capture prompt text and the document base revision at acceptance. Apply workflow, phase, and document revision together after outgoing execution settles. - Hold conflicting document edits for recovery. Preserve cancellation, replay, and restart behavior. - Show current workflow names and captured transition names/phases in Work, history, and Audit. - Update instructions, API/tool contracts, capability maps, and the isolated workflow journey. - Pin bounded workflow prompt reads to a content fingerprint; continuation conflicts on edits and can restart safely. - Remove experimental Cases/workflow/Mermaid guidance from the always-on native prompt. Extract runtime-context prompt strings into a CODEOWNERS-protected constants file while preserving the public imports. - Refresh Copilot’s pinned source identity to v57, including the extracted prompt file and retaining historical identities. ## Verification Local checks: repository `pnpm -r typecheck` and `pnpm build` pass on the prompt-extraction tree (`88222d45d8`). Full repository coverage previously completed through the supported isolated shard groups (1,082 general server suites, 161 serialized suites, and all three workspace groups). The earlier default serial run was interrupted and remains incomplete. The first sharded run retained the failures described below; targeted corrections and separate limited-concurrency reruns pass. All CI jobs, security checks, and the 5/5 Greptile review passed on `fc1bd79c28`. Fresh CI and review are pending for the prompt extraction in `88222d45d8`. Focused verification passes: real-database handoff recovery and reuse; 2,002 native-tool/API contract checks; 144 Runner catalog/discovery checks; 27 Runner context/coverage checks; 37 case/Audit UI checks; and Product E2E oracle calibration. Token gates, migration safety/numbering, module boundaries, capability maps, and generated tool/Storybook catalogs pass. The final route/context corrections pass 49 API/native-tool tests, 90 real-database handoff tests, and server typecheck; 142 Copilot installation/sidecar tests pass with the refreshed identity. Greptile’s first review found that successive prompt pages could mix definitions after an edit. The fix returns a content fingerprint, requires it for continuation, and rejects edits with 409. Strict live/catalog schemas pass (171 Runner checks). The 2,005 API/OpenAPI tests pass; the initial native regression caught compression’s weak ETag prefix, and all 23 native integration tests pass after handling it. The regression covers same-length edits and a four-byte restart window. No stored version fields or entities were added. The prompt extraction passes all 462 checks across six focused suites, including persisted-context recovery and provider transport. The first local transport attempt lacked the required debug Runner binaries (102 failures, retained); after building the normal prerequisites, all 283 tests in that suite pass without source or assertion changes. Existing saved snapshots retain their recorded prompt text, and new executions use core-only prompt v7. The extracted prompt source is now covered by both CODEOWNERS and Copilot’s source fingerprint. Live-provider evidence is separate from those simulated checks: the isolated API-key campaign `reusable-workflows-api-final-2026-10-21` passed with local code matching `a1d73b4652`. Four actual Codex API runs read the adopted task documents, moved one case A → B → A on the same task, completed normally, and passed all nine durable checks. Browser assertions verified the current workflow/phase, historical names/phases, five handoff records including terminal completion, and the completed original task. Cleanup passed. The local report remains under `tests/runner-e2e/results/reusable-workflows-api-final-2026-10-21/`; no credentials or screenshots are committed. CI initially caught an omitted route-inventory entry and a stale pinned instruction-source hash; both are corrected and separately verified. Local serialized coverage also recorded an unrelated environment-route socket disconnect and activity-route timeout; their separate 11-test and 17-test suites passed without code or timeout changes. An unchanged chunked-webhook check also passed its separate four-test rerun. The initial adapter/database workspace groups hit time and resource limits under default worker counts; complete reruns with `VITEST_MAX_WORKERS=2` pass, including all 55 database files and 211 tests. The UI/CLI groups passed 837 files and 9,442 tests in the original run. Original failures remain retained and are not regraded. Earlier failed live campaigns remain unchanged: subscription HTTP 429 before agent execution; an obsolete exact-four-row oracle that rejected the valid terminal fifth row; and a chat-marker expectation that did not account for the existing explicit-comment presentation policy. The final fixture requires terminal completion and places the marker in its authoritative comment. Positive/negative oracle tests and the fresh live campaign pass. ## Risks - The additive migration adds a table and nullable case/handoff fields. Existing rows keep null workflow metadata; no assignments are inferred. - Adoption can wait on an intervening task-document edit. Recovery must cancel the old intent and accept a new one after readback. - Shared definition edits affect future adoptions. Existing case copies keep their revisions. - The feature remains experimental. This PR does not merge or deploy the change. - Live acceptance covers native Codex before the prompt extraction; this cleanup has contract/transport verification rather than a new live-provider campaign. The refreshed Copilot identity has static compatibility evidence, not new live-provider qualification. ## Model Used OpenAI Codex, configured model ID `gpt-6-astra`, xhigh reasoning. Used terminal execution, file editing, and tool calls. The active context-window size is not exposed. ## 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>
25 KiB
Cases
Cases are agent-owned work records for durable outputs such as blog posts, research packets, release notes, incidents, QA runs, or generated asset sets. They are company-scoped and live beside issues: issues coordinate work, while cases preserve the structured object an agent is producing.
Cases are experimental and must be enabled with experimental.enableCases.
If a route returns 403 Cases are disabled, stop and report that the operator
must enable cases before the skill can use this surface.
Reusable workflows.
Under the same Cases flag, use list_workflows to discover company-shared
name-and-prompt definitions, read_workflow to read one, and create_workflow
or update_workflow to author explicit reusable instructions. Creation needs
name, prompt, and idempotencyKey; update needs workflowId, a retry key,
and a name or prompt. These definitions are shared across the company. Case
documents are never automatically published into them.
Native create_case and REST kickoff accept either workflowId or the
inline workflow_document_text described below; kickoff is still required.
Adoption copies the prompt into the primary task's ordinary workflow document.
Read that task document for current instructions, which may contain case-specific
edits. Editing the shared definition leaves existing copies unchanged.
On an agent handoff_case, omit workflowId to preserve the current definition
and document. Supply an ID to adopt its current prompt, including the current ID
to refresh explicitly. Supply null to leave the workflow while retaining task
instructions. Free-text phases and case types are independent of definitions.
The handoff captures the selected prompt when accepted and applies it only on
transfer. If someone edits the task document in between, the handoff is held;
read current state, cancel the unapplied handoff, then submit new intent. Do not
overwrite intervening edits. The same case/task and document history survive
A → B → A. Generic case updates cannot change workflow routing.
read_workflow returns bounded JSON text in data. Continue with the same
workflow ID, returned snapshot, and responseText: {offsetBytes: <nextOffsetBytes>}
until responseText.nextOffsetBytes is null, then parse the concatenated JSON.
An intervening edit returns 409; restart at offset zero without snapshot.
Mutation responses contain metadata.
Core Model
A case has:
identifier: server-assigned display id such asPAP-C42caseType: skill-owned type such asblog_post,image_assets, orincidentkey: optional deterministic upsert key inside(companyId, caseType)titleand optionalsummarystatus:draft,in_progress,in_review,approved,done, orcancelledfields: JSON object owned by the skill using the caseparentCaseId: optional parent case for child workworkIssueId: optional primary workflow task; later handoffs reuse itphaseandhandoffVersion: current applied phase and observed routing revision- documents, attachments, issue links, labels, and events
Use deterministic caseType + key when a skill may be retried. Repeating
POST /api/companies/:companyId/cases with the same caseType and key
upserts the same case instead of creating a duplicate.
Upsert Semantics
POST /api/companies/:companyId/cases creates or upserts a case.
Request:
{
"caseType": "blog_post",
"key": "launch-announcement",
"title": "Launch announcement",
"summary": "Draft launch post for operators.",
"status": "draft",
"fields": {
"slug": "launch-announcement",
"target_audience": "operators"
}
}
Response:
201when a new case was created200when an existing(caseType, key)case was updated
Field behavior on upsert:
titleis required and replaces the previous title.projectId,summary,status,fields, andparentCaseIdreplace the previous value when present.- Omitted optional values preserve the previous value during upsert.
fieldsis replaced as a whole object when provided. It is not deep-merged. Send the complete desired JSON object each time.- Concurrent retries with the same
(caseType, key)converge to one case.
Do not use a random key for retryable skills. Use a stable content slug,
external id, source URL hash, or parent-derived request key.
Start And Hand Off Work
Native create_case always creates a case and starts its first ordinary task.
kickoff and either workflowId or workflow_document_text are required; missing kickoff or both instruction sources is rejected
before any records are created. Provide the first agent, phase, instructions,
the full workflow description, and optional initial task documents:
{
"idempotencyKey":"csv-export-kickoff-1",
"caseType":"software_change",
"title":"Add a CSV export",
"workflow_document_text":"Agent ownership: triage=<triage-agent-uuid>, implementation=<implementation-agent-uuid>, verification=<verification-agent-uuid>. Transition ready: triage saves clear scope and research, then hands the same task to implementation; ask material questions and wait for answers first. Transition candidate_ready: implementation saves changes and check results, then hands off to verification. Transition changes_requested: verification saves concrete defects and returns to implementation; after two failed correction rounds ask for human direction. Transition verified_and_approved: checks and required human approvals are accepted; request handoff to done and complete the same task normally.\n\n```mermaid\nstateDiagram-v2\n [*] --> triage\n triage --> implementation : ready\n implementation --> verification : candidate_ready\n verification --> implementation : changes_requested\n verification --> done : verified_and_approved\n done --> [*]\n```",
"kickoff":{"toAgentId":"<triage-agent-uuid>","phase":"triage","instructions":"Research the request, then hand this same task to implementation."},
"initialDocuments":[{"key":"brief","title":"Export brief","body":"Export the filtered rows as CSV. Preserve the current column order."}]
}
The harness saves workflow_document_text verbatim as the ordinary task document
workflow, titled Workflow, in Markdown format. It creates the primary task
and all seeded documents in the same transaction before delivery. Documents do not belong to
the case. Keys follow ordinary task document rules (trimmed/lowercased,
1–64 characters); duplicate keys and the reserved workflow key are rejected in
initialDocuments. A kickoff accepts up to 20 additional documents, with at most
200,000 body characters including the workflow text. There is no pending document
seed storage on the case. Read read_document({"key":"workflow"}) before deciding
what to do or where to hand off. The harness supplies its reference; it does not
repeat the entire body in every prompt. Update the workflow through ordinary task
document revision tools when authorized; do not resend it on later handoffs.
Recommend both a fenced Mermaid stateDiagram-v2 map and prose explaining
every transition. Include the objective, phase owners (actual agent IDs), expected
outputs, transition conditions, human waits, correction limits, and completion
criteria. For example:
stateDiagram-v2
[*] --> triage
triage --> specification : needs_spec
triage --> implementation : ready
specification --> implementation : spec_approved
implementation --> verification : candidate_ready
verification --> implementation : changes_requested
verification --> done : verified_and_approved
done --> [*]
Follow the map with prose that defines its named outcomes:
| Transition | Prose definition |
|---|---|
triage → specification (needs_spec) |
Scope or acceptance criteria need a specification. Save findings and unresolved questions, then assign the named specification agent. |
triage → implementation (ready) |
Scope, inputs and acceptance criteria are clear. Save the research and give the named implementation agent its evidence and constraints. |
specification → implementation (spec_approved) |
The required approval for the saved spec revision is accepted. While pending, use the normal review wait; resume only after approval. |
implementation → verification (candidate_ready) |
Save the candidate, artifacts and actual check results. Give the named verification agent the read paths and acceptance criteria. |
verification → implementation (changes_requested) |
Persist concrete defects and failing evidence before returning to the implementation agent. Allow at most two correction rounds, then ask for human direction. |
verification → done (verified_and_approved) |
Evidence satisfies acceptance criteria and required reviews/approvals are accepted. Request to: "done" and complete the same task through its normal gates. |
Use stable state IDs matching agent phase names and map every active phase to its
actual agent ID. Define each transition's trigger, evidence, destination and any
wait/resume, correction or escalation rules; a diagram alone is insufficient.
Arrow labels are not executable guards. Paperclip stores this document as
task data and does not parse or enforce its graph. Human waits use existing
question/review/approval mechanisms, and done still requires accepted normal
task completion. See Mermaid state diagrams.
Alternatively use native handoff_case or
POST /api/companies/:companyId/case-handoffs:
{
"idempotencyKey": "launch-triage-1",
"case": {
"create": {
"caseType": "software_change",
"title": "Add a CSV export",
"summary": "Deliver the requested export with verification and review."
}
},
"toAgentId": "<triage-agent-uuid>",
"phase": "triage",
"workflow_document_text": "Agent ownership: triage=<triage-agent-uuid>, implementation=<implementation-agent-uuid>, verification=<verification-agent-uuid>. Transition ready: triage saves clear scope and evidence, then sends the same task to implementation; wait for answers to material questions first. Transition candidate_ready: implementation saves changes and actual check results, then sends the task to verification. Transition changes_requested: verification saves concrete defects before returning to implementation; after two failed correction rounds request human direction. Transition verified_and_approved: checks and required approvals are accepted; hand off to done and complete normally.\n\n```mermaid\nstateDiagram-v2\n [*] --> triage\n triage --> implementation : ready\n implementation --> verification : candidate_ready\n verification --> implementation : changes_requested\n verification --> done : verified_and_approved\n done --> [*]\n```",
"instructions": "Research the request. Ask material questions or hand this same task to the implementation agent."
}
The harness creates the initial ordinary task and assigns it. For an existing
case, send case: {"id": "<case-uuid>"}, expectedHandoffVersion, and
expectedIssueStatusVersion from fresh authorized context. The existing primary
task is reused automatically. A passive case may explicitly bind an existing
ordinary task with bindIssueId and its observed status version.
New-case API creation also requires top-level workflowId or workflow_document_text. Starting
the first new task of an older passive case requires it too. New-case binding
creates the bound task's workflow document only if that key is absent; an
existing workflow key conflicts for inline text. An explicit workflowId adopts the selected prompt as a new revision instead. Later primary-task
handoffs omit workflow text. The agent-handoff input accepts initialDocuments only when
it creates the first primary task, including for an existing passive case.
Existing-task handoffs, task binding and to: "done" reject initial documents;
use ordinary task document tools to revise existing work.
Save findings, documents, and artifacts before handing off. Include the next
phase's objective, relevant evidence, remaining work, and any constraints in
instructions. Reuse a stable idempotency key when retrying the same operation;
different content under that key is a conflict. Reload on a version conflict.
The source run is provenance; a later run can recover the same principal's receipt.
After an accepted self-handoff, end the turn without marking the task done. The
harness keeps the recipient waiting until your execution and workspace settle.
202 means intent accepted; delivered means the ordinary wake queue accepted
it. Neither is proof the recipient ran. Handoff to the same agent is also a new
phase/turn. Each returning agent may resume its own task session.
Separate work uses ordinary task creation plus a case link. It does not replace
the primary task. Human clarification, specification approval, code review, and
final approval use existing interactions. A phase called review is not a verdict.
Ordinary task completion leaves case status unchanged. To finish the case as
part of completing its primary task, hand off to the terminal destination:
{
"idempotencyKey": "launch-finished-1",
"case": { "id": "<case-uuid>" },
"to": "done",
"instructions": "Implementation and verification are complete; saved evidence is in the case documents.",
"expectedHandoffVersion": 4,
"expectedIssueStatusVersion": 7
}
This records an intent to close an existing case with a primary task; it does
not create work, transfer assignment, or satisfy completion criteria. The
receipt has callerMustYield: false and state: awaiting_completion. Finish
the same task normally, including any required review, human answer or approval.
Once task completion is accepted, its transaction closes the case, sets phase
done, and settles the terminal handoff as completed. If the task was already
done, closing is immediate. Pending review or user input keeps the case open.
Do not change case status separately. An accepted terminal request survives
restart; Stop, task cancellation or reassignment cancels it. Cancelling the
request leaves the task untouched. A replay returns the original receipt.
History is at GET /api/cases/:id/handoffs?limit=20&cursor=.... Authorized task
controllers can POST to /api/cases/:id/handoffs/:handoffId/retry or /cancel.
Retry delivers the same intent; cancellation never reverses an applied assignment.
Cases retain task/project permission boundaries, including copied run output. An inaccessible or deleted source cannot be bypassed by unlinking it. For an existing private passive case, select an agent who already qualifies for those source permissions. Handoff does not grant unrelated task/project access.
Example factory instructions
Use the supplied company agent IDs for triage, specification, implementation, and verification. Operate on the case's primary task throughout:
- Triage researches the request and saves findings on the case. Clarify material ambiguity through the normal question interaction. Park work that should not proceed; otherwise hand off to specification or implementation.
- Specification saves a revisioned
specdocument and requests normal human confirmation. After acceptance, hand the task to implementation. - Implementation builds the approved work, registers artifacts/PR references, records checks, and hands the task to verification.
- Verification checks the actual result and saves its report. Return concrete defects to implementation on the same task. After two unsuccessful correction rounds, request human direction rather than starting an unbounded loop.
- Follow existing code-review and human-approval requirements before completion
or shipping. The final agent calls
handoff_casewithto: "done", the existing case and current versions, then completes the same task normally. Monitoring may create a new case for a new incident.
Routing belongs in these instructions. Paperclip does not require a pipeline or preconfigured phase graph to execute them.
Read And Search
Native runner agents have production case tools in standard work mode when cases are enabled:
| Tool | Use |
|---|---|
create_case |
Create a case and start its first task with required kickoff and either workflowId or workflow_document_text, plus optional initialDocuments. |
read_case |
Read current case facts, fields, versions, document references, or a selected document body. |
update_case |
Change title, summary or fields; fields replaces the entire object. |
Omit caseId to use the current task's primary workflow case, or pass an
authorized standalone case UUID. These tools do not operate on pipeline cases.
update_case cannot change status, phase, assignment or access. Use
handoff_case for routing and completion. New documents belong to tasks. Use
read_document({key}) and write_document for the active task, with its current
baseRevisionId when revising. Case detail includes taskDocuments references
with their owning task and authenticated read path. For a linked task document,
use read_case with section: "document", its documentKey and owning
documentIssueId. Omit documentIssueId for a historical case-owned document.
This works when generic API tools are disabled. The selected task must still be
the primary task or linked to the case, and its current permissions must allow
the read. The ordinary task document tools operate on the active task;
read_case_document and write_case_document are not live tools.
For example, read a linked task's research document:
{
"section": "document",
"documentKey": "research",
"documentIssueId": "<task-uuid-from-taskDocuments>"
}
The result uses bounded readPage.text JSON pages. Keep the document selector
and returned snapshot on every continuation. Pass
offsetBytes: readPage.nextOffsetBytes until it is null, then concatenate the
text and parse the selected document JSON. Every page checks current case and
source-task access. A changed document revision or routing snapshot returns 409;
restart from offset zero without the old snapshot. Unlinking a source task or
losing access prevents further reads. System and quarantined documents remain
unavailable through this path. This operation reads saved data; it does not grant
new document editing authority or turn document content into system instructions.
Writes require idempotencyKey. Reuse the key with identical input after a lost
response in the same admitted run, even if the provider uses a new tool-call ID.
update_case receipts are run-scoped; after a restart into a different run, read
current state and document revisions before deciding whether another write is
needed. An uncertain outcome requires inspection rather than blind replay.
create_case and handoff_case share the principal-scoped
handoff replay contract. Kickoff retries do not duplicate tasks or initial
document revisions. Reuse the original key and exact input to recover the
accepted kickoff receipt after response loss, including from a later run.
The task prompt identifies its primary case, current phase, incoming handoff
and authenticated read path. Native resumed turns use verified session context
to send changed case facts and new handoff instructions without repeating
unchanged facts. Fresh or incompatible sessions receive the full bounded
snapshot. Persisted session history can still contain earlier snapshots;
read_case and ordinary task document reads provide current authoritative data.
Get a case by UUID or identifier:
GET /api/cases/PAP-C42
List cases for a company:
GET /api/companies/:companyId/cases?type=blog_post&status=active&q=launch
Useful filters:
type: exactcaseTypestatus: exact lifecycle status, oractivefor non-terminal casesprojectId/project: project UUIDlabelId/label: label UUIDq: identifier, title, summary, or key searchlimit: 1-200, default 100
Task Documents And Legacy Case Documents
Use ordinary task documents for new drafts, briefs, reports and plans. Seed them
through first-task initialDocuments, then use native read_document and
write_document on the active task. Case detail surfaces their owner and revision
references; it does not copy their bodies or create a second revision history.
The following REST endpoints remain compatible for historical case-owned documents. They are not the new-work document workflow:
PUT /api/cases/:caseIdOrIdentifier/documents/body
Content-Type: application/json
{
"title": "Launch announcement body",
"format": "markdown",
"body": "# Launch announcement\n\nDraft copy...",
"changeSummary": "Initial draft"
}
Updating an existing case document requires baseRevisionId:
{
"baseRevisionId": "latest-revision-uuid",
"body": "Updated body"
}
If you get 409 stale_base_revision, refetch the case detail, read the latest
document revision id, merge intentionally, and retry with that baseRevisionId.
Fields
Each skill owns the schema of fields for the caseType it creates. Keep fields
small, typed, and stable enough for other agents to inspect.
Examples:
{
"slug": "launch-announcement",
"target_audience": "operators",
"publish_url": "https://example.com/blog/launch-announcement"
}
Patch fields or status with:
PATCH /api/cases/:caseIdOrIdentifier
Content-Type: application/json
{
"status": "in_review",
"fields": {
"slug": "launch-announcement",
"target_audience": "operators",
"publish_url": "https://example.com/blog/launch-announcement"
}
}
Remember: fields replaces the whole object when present.
Issue Links
Link cases to issues explicitly when needed:
POST /api/cases/:caseIdOrIdentifier/links
Content-Type: application/json
{
"issueId": "issue-uuid",
"role": "reference"
}
Roles:
origin: the issue/run that created the casework: an issue/run that changed the casereference: related issue context
Agent run writes auto-link the run's issue when Paperclip can resolve it from
the run JWT or X-Paperclip-Run-Id. Creation/upsert writes use origin; later
document, patch, and attachment writes use work when no link already exists.
You do not need to manually link the current issue before writing the case.
Child Cases
Create child cases by setting parentCaseId to the parent case UUID.
{
"caseType": "image_assets",
"key": "launch-announcement:hero-images",
"title": "Hero images for launch announcement",
"parentCaseId": "parent-case-uuid",
"fields": {
"required_assets": ["hero", "social-card"]
}
}
Use child cases when the output has independently inspectable pieces or when another agent can work on a bounded part without editing the parent case body.
Attachments
Attach generated files with multipart form data:
POST /api/cases/:caseIdOrIdentifier/attachments
Content-Type: multipart/form-data
file=@hero.png
The server records an asset and adds an attachment_added case event.
Lifecycle
Use the lifecycle consistently:
draft: case exists but useful work has not startedin_progress: an agent is actively producing or revising itin_review: ready for reviewer, board, or downstream approvalapproved: accepted but not finally shipped or archiveddone: complete and no further action remainscancelled: intentionally abandoned
Terminal statuses are done and cancelled; setting either records
completedAt. Moving back to a non-terminal status clears completedAt.
Worked Blog Post Example
Create or upsert the parent blog post:
POST /api/companies/:companyId/cases
Content-Type: application/json
{
"caseType": "blog_post",
"key": "paperclip-cases-launch",
"title": "Introducing Paperclip Cases",
"summary": "Blog post explaining the cases surface for agent outputs.",
"status": "in_progress",
"fields": {
"slug": "paperclip-cases-launch",
"target_audience": "AI company operators",
"publish_url": null
}
}
Start drafting on the case's first task and seed its body document:
{
"idempotencyKey": "paperclip-cases-launch-draft",
"case": {"id": "<blog-case-uuid>"},
"expectedHandoffVersion": 0,
"toAgentId": "<writer-agent-uuid>",
"phase": "draft",
"instructions": "Finish the draft on this same task, then hand it to editorial review.",
"initialDocuments": [{"key":"body","title":"Introducing Paperclip Cases","body":"# Introducing Paperclip Cases\n\n..."}]
}
Send this input to handoff_case. The writer uses ordinary task document tools
for subsequent revisions; the case page rolls up that same document.
Create the child image-assets case:
POST /api/companies/:companyId/cases
Content-Type: application/json
{
"caseType": "image_assets",
"key": "paperclip-cases-launch:image-assets",
"title": "Image assets for Introducing Paperclip Cases",
"parentCaseId": "parent-case-uuid",
"status": "in_progress",
"fields": {
"slug": "paperclip-cases-launch",
"required_assets": ["hero", "social-card"],
"publish_url": null
}
}
Attach generated assets to the child, then patch both cases as they move through review:
PATCH /api/cases/PAP-C42
Content-Type: application/json
{
"status": "in_review",
"fields": {
"slug": "paperclip-cases-launch",
"target_audience": "AI company operators",
"publish_url": "https://example.com/blog/paperclip-cases-launch"
}
}
If the same skill retries the example with the same keys, it updates the parent and child cases rather than creating duplicates.