diff --git a/CHECKLIST.md b/CHECKLIST.md index c643fee..ce81ff6 100644 --- a/CHECKLIST.md +++ b/CHECKLIST.md @@ -4,21 +4,29 @@ Tick each line. Nothing after the first unchecked line should be started early. ## Before you start -- [ ] Paperclip answers: `curl -s -H "Authorization: Bearer $PAPERCLIP_BOARD_KEY" http://127.0.0.1:3100/api/health` -- [ ] Go is installed: `go version` -- [ ] You are using the **user** key (`PAPERCLIP_BOARD_KEY`), not the agent key. +- [x] Paperclip answers: `curl -s -H "Authorization: Bearer $PAPERCLIP_BOARD_KEY" http://127.0.0.1:3100/api/health` → 200. +- [ ] Go is installed: `go version` → **still not installed** (checked 2026-11-10). Needed before Milestone 2. +- [x] You are using the **user** key (`PAPERCLIP_BOARD_KEY`), not the agent key. -## Milestone 1 — one task, really run (no interface) +## Milestone 1 — one task, really run (no interface) — PASSED 2026-11-10 -- [ ] Read `docs/ENVIRONMENT-FACTS.md` for the company id, agent ids and toolchain facts. -- [ ] Make a sandbox project (payload in `docs/PAPERCLIP-API-NOTES.md`) with two tasks, the second blocked by the first. -- [ ] Assign the first task to `Builder` and force a wake with `POST /api/agents/{id}/heartbeat/invoke`. **Unproven** — if no run appears, record the response and stop. -- [ ] Watch: the task goes `todo` → `in_progress` → `done`. -- [ ] Confirm a comment written by Pi holds what it did. -- [ ] Confirm the cost appears: `GET /api/issues/{id}/cost-summary`. -- [ ] Confirm Paperclip made a git worktree for the task, and where it put it. +- [x] Read `docs/ENVIRONMENT-FACTS.md` for the company id, agent ids and toolchain facts. +- [x] Make a sandbox project (payload in `docs/PAPERCLIP-API-NOTES.md`) with two tasks, the second blocked by the first. + → `spike-2026-11-10`, `AUD-159`, `AUD-160` (`AUD-160` blocked by `AUD-159`). +- [x] Assign the first task to `Builder`. The assignment alone woke it in 0.1 s; + `heartbeat/invoke` was not needed. → wake `source=assignment`, run `aae77e89-…`. +- [x] Watch: the task goes `todo` → `in_progress` → `done` (00:01:25 → 00:01:53). +- [x] Confirm a comment written by Pi holds what it did. → commit `b093301`, comment `822f2924-…`. +- [x] Confirm the cost appears: `GET /api/issues/{id}/cost-summary`. → 1 cent, run count 1. +- [x] Confirm where the work happened. → **No worktree.** Paperclip ran it in + `/tmp/pc-sandbox-2026-11-10` on `main`, because `enableIsolatedWorkspaces` is off on + this instance. See `docs/ENVIRONMENT-FACTS.md`. -Stop here if any line fails. Write down what you saw. Do not start the screens. +PASSED, with one correction to the plan: the per-task worktree is not free — it needs an +instance-wide experimental flag turned on by the person. + +The second task was left `todo` and unassigned. The `issue_blockers_resolved` wake has not +been measured. ## Milestone 2 — task tree and reply box diff --git a/docs/ENVIRONMENT-FACTS.md b/docs/ENVIRONMENT-FACTS.md index b82c9a0..2cec9f8 100644 --- a/docs/ENVIRONMENT-FACTS.md +++ b/docs/ENVIRONMENT-FACTS.md @@ -29,26 +29,56 @@ Everything here was measured with a command. Anything not measured is marked unp `POST /api/companies/{companyId}/projects` with `name`, `status: "backlog"` and `workspace: { name, sourceType: "git_repo", cwd, isPrimary: true }`. -## The unproven step: does a heartbeat wake an agent? +## Does a heartbeat wake an agent? PROVEN, 2026-11-10 -Measured: a task can be created, given a blocker, and commented on. **Not** measured: that -assigning a task causes Pi to wake and work. +It does, and the wake is not a timer. Assigning a task woke `Builder` within one tenth of +a second, with no call to `heartbeat/invoke`. -The endpoint that should start it exists: +Measured on `spike-2026-11-10` / `AUD-159`: ``` -POST /api/agents/{id}/heartbeat/invoke (empty body) +00:01:24.891Z PATCH /api/issues/AUD-159 {"assigneeAgentId":"403de1f7-..."} -> 200 todo +00:01:24.937Z wake request recorded: source=assignment reason=issue_assigned +00:01:25.036Z run aae77e89-... claimed, adapter pi_local, issue -> in_progress +00:01:51.351Z agent comment posted (commit b093301) +00:01:53.153Z issue -> done +00:01:59.377Z run -> succeeded ``` -Test in this order, and write down the response at each step: +So the four documented wake sources all reach an agent: a timer tick, an assignment or a +new comment on its own task, a person clicking wake, and a routine firing. The timer is +opt-in and is off for most agents; `Builder` currently has `heartbeat.enabled: true`, so +it also polls. Assignment alone is enough. -1. Assign a task: `PATCH /api/issues/{id}` with `{"assigneeAgentId":""}`. -2. Either wait for a scheduled heartbeat, or force one: - `POST /api/agents/{agentId}/heartbeat/invoke`. -3. Read `GET /api/issues/{id}/runs` and `GET /api/issues/{id}/activity` to see whether a - run started. -4. If no run appears, the wake needs something we have not configured (a routine, a - schedule, or a wake trigger). Record the facts; do not build a workaround. +`POST /api/agents/{id}/heartbeat/invoke` was never needed and remains untested. The +documented manual wake is `POST /api/agents/{id}/wakeup` with +`{"source":"on_demand","triggerDetail":"manual","forceFreshSession":...}`. + +A wake can still be refused without any error reaching us. Read +`GET /api/issues/{id}/diagnostics/wakes` — it shows each wake request, its claimed run, +and a plain-English diagnosis. Refusal reasons seen in the documentation: the agent is +paused, its budget is exhausted, its cooldown has not elapsed, or its run slot is full. + +## Isolated workspaces are switched off on this instance + +The project was created asking for a git worktree per task (`executionWorkspacePolicy` +with `workspaceStrategy.type: "git_worktree"`). **No worktree was made.** The run worked +directly in `/tmp/pc-sandbox-2026-11-10` and committed onto `main`. The task's +`executionWorkspaceId` and `currentExecutionWorkspace` both read `null`. + +`GET /api/instance/settings/experimental` returns: + +``` +"enableIsolatedWorkspaces": false, +"enableIsolatedWorkspacesByDefault": false, +"enableWorktreeRunExecution": false, +``` + +With `enableIsolatedWorkspaces` false, Paperclip discards a task's workspace settings +before the run is dispatched, so the mode falls back to the shared project folder. The +worktree code is present in the server (`git worktree add` in +dist/services/workspace-runtime.js). Switching it on is an instance-wide experimental +setting and a decision for the person, not a setting for one project. ## Toolchain diff --git a/docs/PAPERCLIP-API-NOTES.md b/docs/PAPERCLIP-API-NOTES.md index d773f4b..f680dd0 100644 --- a/docs/PAPERCLIP-API-NOTES.md +++ b/docs/PAPERCLIP-API-NOTES.md @@ -133,3 +133,90 @@ A project can be created with an `executionWorkspacePolicy` whose `workspaceStra 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. + +--- + +# Milestone 1 measured, 2026-11-10 (writes, sandbox only) + +Everything below was run with the **user key** (`PAPERCLIP_BOARD_KEY`). The key is never +printed. The sandbox is `spike-2026-11-10` and its scratch repository +`/tmp/pc-sandbox-2026-11-10`. Both are safe to delete. + +## The order that works + +The board guide's rule holds: never create a task and assign it in the same action. The +four calls, in order, with the result each one returned: + +| Call | Result | +|---|---| +| `POST /api/companies/{company}/projects` | 201, project `17adbb47-f966-4a82-a8ef-e48c941443ea`, workspace `4d4eb32a-08a8-4e3e-88c9-2a8cedfc288b` | +| `POST /api/companies/{company}/issues` ×2 | 201, `AUD-159` `34abf83f-…`, `AUD-160` `21d1f49e-…`, both `todo`, unassigned | +| `PATCH /api/issues/AUD-160` `{"blockedByIssueIds":["34abf83f-…"]}` | 200, echoed `blockedBy` with `AUD-159`; diagnostics then said `unresolvedBlockerCount: 1` | +| `PATCH /api/issues/AUD-159` `{"assigneeAgentId":"403de1f7-…"}` | 200, `todo` | + +The activity record of the assignment names the person, not an agent: +`actorType: "user"`, `authorizationReason: "allow_board_actor"`. That is the audit trail +the user key exists for. + +## The wake, timed + +``` +00:01:24.891Z PATCH assigneeAgentId -> 200 +00:01:24.937Z wake requested source=assignment reason=issue_assigned status=claimed +00:01:25.036Z run aae77e89-7059-43b4-9154-972282770212 started, issue -> in_progress +00:01:51.351Z comment posted by agent 403de1f7-… +00:01:53.153Z issue -> done +00:01:59.377Z run -> succeeded (resultJson.stopReason = "completed") +``` + +`GET /api/issues/AUD-159/diagnostics/wakes` returned that single wake request and the +plain-English line *"The most recent wake for AUD-159 is currently claimed for +issue_assigned."* This endpoint answers the question we used to have to guess at: it names +each wake, the run it produced, and why a wake did not happen. + +H3 is now **PASS**: a real agent woke on assignment, worked, commented and closed the task +with no person in the loop and no timer. + +## What the agent reported + +Comment `822f2924-dd2f-4f26-9046-c7801f2196a9`, author `agent`, id `403de1f7-…`: + +> Created `hello.txt` … Commit hash `b093301148b62ab0f2a34bec4f31d97a26a212e9` … Working +> directory (absolute path): `/tmp/pc-sandbox-2026-11-10` … Branch: `main`. + +Checked on disk: `git log --oneline` shows `b093301 add hello.txt` on `main`, the file +holds the expected line, and `git status` is clean. + +## Cost, read back + +`GET /api/issues/AUD-159/cost-summary` → +`costCents: 1`, `inputTokens: 101002`, `outputTokens: 1515`, `runCount: 1`, +`runtimeMs: 34341`. The run's own `usageJson` says `costUsd: 0.00749434`. + +## No worktree was created, and why + +The task's `executionWorkspaceId` and `currentExecutionWorkspace` are `null`. The run +worked in the project folder on `main`, so a second task would collide rather than work on +its own copy. + +Cause, measured: `GET /api/instance/settings/experimental` returns +`enableIsolatedWorkspaces: false` and `enableWorktreeRunExecution: false`. With the first +flag off, Paperclip throws away the task's workspace settings before dispatch, so the mode +resolves to the shared project folder even though the project asked for `git_worktree`. + +The supporting code is in the installed server: +`dist/services/heartbeat.js` sets `issueExecutionWorkspaceSettings = null` when the flag is +off, and `dist/services/workspace-runtime.js` is where `git worktree add` would run. +Default worktree location is `/.paperclip/worktrees/`; default branch +template is `{{issue.identifier}}-{{slug}}`. + +**Consequence for the plan.** The "Paperclip makes the worktree for us" assumption is +true only with an experimental instance setting switched on. Until a person turns it on, +every task shares one folder and merges happen on `main` with no branch and no review +step. That is a decision to put to the person, not a bug to work around. + +## Second task left where it is + +`AUD-160` stayed `todo`, unassigned, blocked by a task that is now `done`. Nothing woke, +because no agent is assigned to it. Whether the `issue_blockers_resolved` wake fires for an +assigned dependent task is still unmeasured.