Milestone 1 measured: assignment wakes Pi, task ran end to end; no worktree because isolated workspaces are off
This commit is contained in:
32
CHECKLIST.md
32
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
|
||||
|
||||
|
||||
@@ -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":"<agent id>"}`.
|
||||
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
|
||||
|
||||
|
||||
@@ -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 `<repo>/.paperclip/worktrees/<branch>`; 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.
|
||||
|
||||
Reference in New Issue
Block a user