6.4 KiB
Paperclip API — read-only probe, 2026-11-09
Probe only: no writes, no task created, nothing in the existing projects touched. All
requests were GET with Authorization: Bearer $PAPERCLIP_API_KEY read from the
environment; the key is never printed here.
Where it runs
- Local server:
http://127.0.0.1:3100, pid 1588, started 2026-11-05. From~/.paperclip/instances/default/runtime-info.json. - Public URL:
https://paperclip.lab.audasmedia.com.au. - Postgres:
postgres://paperclip@127.0.0.1:5433/paperclip(embedded, local). - CLI:
paperclipai2026.916.1, managed install under~/.paperclip/cli/installs.
Endpoints that answered
| Request | Result |
|---|---|
GET /api/health |
200, 899 bytes |
GET /api/openapi.json |
200, 1.1 MB — 685 paths |
GET /api/agents/me |
200 — identity, company, adapter |
GET /api/companies/{companyId}/issues?limit=200 |
200 — 145 issues |
GET /api/issues/{id} |
200 |
GET /api/issues/{id}/comments |
200 |
GET /api/issues/{id}/activity |
200 |
GET /api/issues/{id}/cost-summary |
200 |
GET /api/issues?limit=5 |
400 — "Missing companyId in path" |
The identity the API answered as
name: Hub, role: general, status: idle, adapterType: **pi_local**,
model omni/deepseek/deepseek-v4-flash. So Pi is the installed worker adapter, not
something we must write.
What already exists in this instance
- 10 projects, 145 issues: 139
done, 2blocked, 2in_progress, 1todo, 1backlog. - Every status the four-screen TUI needs is already a first-class value.
Endpoints the TUI and the executor will need (from the 685 in the spec)
- Read:
/api/companies/{companyId}/issues,/api/issues/{id},/api/issues/{id}/comments,/api/issues/{id}/activity,/api/issues/{id}/children,/api/issues/{id}/diagnostics/blockers,/api/issues/{id}/cost-summary,/api/issues/{id}/runs,/api/issues/{id}/heartbeat-context,/api/agents/me/inbox-lite. - Write (later, not in this probe):
/api/issues,/api/issues/{id}/comments,/api/issues/{id}/checkout,/api/issues/{id}/release,/api/approvals/{id}/approve|reject|request-revision,/api/cases/{caseId}/blockers,/api/issues/{id}/interactions/{interactionId}/respond. - Trees and holds:
/api/issues/{id}/tree-control/state|preview,/api/issues/{id}/tree-holds,/api/issues/{id}/diagnostics/subtree.
Hypotheses
- H1 — the control plane answers. PASS. Health, identity, 145 issues.
- H2 — task with a blocker via API. NOT TESTED (a write; out of scope for a
read-only probe). Note: the two blocked issues returned
blockedByIssueIds: None, so the blocker relation may live in/api/cases/{caseId}/blockersor/api/issues/{id}/diagnostics/blockersrather than on the issue row. This must be established before trusting screen 1. - H3 — a comment wakes an agent. NOT TESTED. The wake plumbing is visible in the
spec (
heartbeat-context,inbox-lite,queued-comments/steer,diagnostics/wakes), and agents run on heartbeats, so the shape is right. Unproven. - H4 — Pi can be a worker. Likely PASS:
adapterType: pi_localis the identity's own adapter, andPiis listed as a supported runtime upstream. - H5 — cost and run history readable. PASS.
/api/issues/{id}/cost-summaryreturnscostCents,inputTokens,cachedInputTokens,outputTokens,runCount.
What this changes
The premise holds: there is a live control plane, with the statuses, threads, activity, cost and blocker diagnostics a four-screen TUI needs, and Pi is already the adapter. The work that remains is small and specific: prove a blocker edge by API, prove a comment wakes an agent, and write the executor that puts a task into a git worktree, runs it and reports back.
Sandbox writes, 2026-11-09 (approved in advance)
Sandbox created: project spike-2026-11-09 (9e3f61f6-cce9-49b1-8083-3130345a04a5),
status backlog, workspace git_repo at /tmp/pc-sandbox (a throwaway repo with one
commit). It is safe to delete.
Two issues created by API, status todo:
AUD-157Spike T1 — write a file (2ce09ea8-…)AUD-158Spike T2 — read that file (abc5d318-…)
The permission rule, learned the hard way
With PAPERCLIP_API_KEY (the agent Hub), every write returned 403:
"Cross-issue writes need a run to attribute them to (Heartbeat run context). Every agent comment and task update is attributed to a heartbeat run so the cross-issue cap can be counted and the audit trail can name who acted for whom."
So an agent key is only usable inside a heartbeat run, with X-Paperclip-Run-Id.
A cockpit acting for the person must use the user credential
(PAPERCLIP_BOARD_KEY), not the agent key. That is a design fact, not an obstacle: the
TUI is the person's surface, and the audit trail correctly refuses anonymous agent
writes.
H2 — a task with a blocker: PASS
PATCH /api/issues/{id} with {"blockedByIssueIds":["<t1>"]} → 200, and the edge reads
back. GET /api/issues/{id}/diagnostics/blockers returns:
readiness: { allBlockersDone: false, isDependencyReady: false, unresolvedBlockerCount: 1 }
blockers: [ "Spike T1 — write a file" ]
Note: the blocker edge is not on the issue list rows (blockedByIssueIds: 0 there).
A tree view must read diagnostics/blockers (or the single-issue GET) per task, not the
list. That is the one place screen 1 could have lied to us.
H3 — a comment lands on a blocked task: PASS
POST /api/issues/{id}/comments with the user key → 201, authorType: user, and the
comment is on the thread of the blocked task. What this does not yet prove is that a
comment wakes an agent: that requires a real agent on the task and a heartbeat. The
wake plumbing is present in the spec (heartbeat-context, inbox-lite,
queued-comments/steer, diagnostics/wakes); the wake itself is unproven.
Updated verdict
H1 pass, H2 pass, H3 partly (comment lands; wake unproven), H4 likely (Pi is the
adapter), H5 pass. The remaining proof is one real agent doing one task, which is
Milestone 1 of plan/92-paperclip-spike.md, not a probe.
One more thing worth knowing
A project can be created with an executionWorkspacePolicy whose workspaceStrategy.type
is git_worktree, with baseRef, branchTemplate, worktreeParentDir, provision and
teardown commands. Paperclip may already do the worktrees we assumed we would build.
That deserves its own probe before any executor is written.