Handover: history, plan, API measurements, checklist, open issues from the OMA attempt
This commit is contained in:
135
docs/PAPERCLIP-API-NOTES.md
Normal file
135
docs/PAPERCLIP-API-NOTES.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 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: `paperclipai` 2026.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`, 2 `blocked`, 2 `in_progress`, 1 `todo`, 1 `backlog`.
|
||||
- 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}/blockers` or
|
||||
`/api/issues/{id}/diagnostics/blockers` rather 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_local` is the identity's
|
||||
own adapter, and `Pi` is listed as a supported runtime upstream.
|
||||
- **H5 — cost and run history readable.** PASS. `/api/issues/{id}/cost-summary` returns
|
||||
`costCents`, `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-157` Spike T1 — write a file (`2ce09ea8-…`)
|
||||
- `AUD-158` Spike 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.
|
||||
Reference in New Issue
Block a user