Files
paperclip-harness/docs/PLAN-INTERFACE.md

628 lines
31 KiB
Markdown

# The plan, and how the interface sits on Paperclip
Written 2026-11-10, after a wrong turn: I split every workspace into three panes without
being asked, and it broke the working layout. It has been reverted. This document says what
exists, what is proposed, and which Paperclip endpoint each thing reads or writes. Nothing
here is invented: every endpoint below was called on this machine and answered.
## What is on screen today
Seven workspaces, one pane each: `Shell>shell`, `Board>board`, `Work>work`, `Reply>reply`,
`New task>new`, `Project>project`, `Repair>repair`. Inside a pane the program draws several
panels (Board draws three), and you switch focus with Tab. That is the state that works.
## Paperclip's objects, and where each one is seen
| Paperclip object | Endpoint, measured | Where it appears now |
|---|---|---|
| company | `GET /api/companies/{id}` | not shown; the id is in the project file |
| project | `GET /api/companies/{id}/projects` | `Project>project` |
| task | `GET /api/companies/{id}/issues`, `GET /api/issues/{id}` | `Board>board`, `Reply>reply` task panel |
| blockers of a task | `GET /api/issues/{id}/diagnostics/blockers` | `Board>board`, per row |
| comment thread | `GET /api/issues/{id}/comments` | `Reply>reply` |
| runs of a task | `GET /api/issues/{id}/runs` | `Work>work` |
| activity | `GET /api/issues/{id}/activity` | `Board>board` needs-you tab, `Work>work` |
| cost per task | `GET /api/issues/{id}/cost-summary` | `Board>board` detail, `Work>work` facts, `Project>project` costs |
| worktree and branch | the task's `currentExecutionWorkspace` | `Work>work` change and facts |
| agents | `GET /api/companies/{id}/agents` | `Project>project` facts and limits, by name and budget |
| **an agent's runs, company-wide** | `GET /api/companies/{id}/heartbeat-runs` | **nowhere yet** |
| **a run's log** | `GET /api/heartbeat-runs/{runId}/log` — 300 KB of JSON lines for one finished run | **nowhere yet** |
| **a run's events** | `GET /api/heartbeat-runs/{runId}/events` | **nowhere yet** |
| agent pause, resume, wake | `POST /api/agents/{id}/pause`, `/resume`, `/wakeup` | **nowhere yet** |
| agent settings | `PATCH /api/agents/{id}` — accepts `role`, `adapterConfig` (the model), `runtimeConfig` (heartbeat), `budgetMonthlyCents`, `status` | **nowhere yet** |
So the honest answer to "where do I see the Pi agents working": **you cannot, yet.** The data
is all there and the runs are already recorded — 281 of them on this instance — but no screen
shows them. That is the largest missing piece, and it is the next workspace.
## Missing: the Agents workspace
This is the one the person keeps asking for and it does not exist. Proposed as one pane, the
same shape as the others, drawing three panels with tabs:
```
Agents>agents
╭─ agents roster runs ╮
│ Campfire idle
│ ● Builder running AUD-163 started 14:12 1m 20s 1 cent
│ ● Hub idle last run 13:58
│ ● Documenter idle
╰───────────────────────────╯
```
- **roster** — every agent: name, state (`idle`, `running`, `needs input`, and Paperclip's
own status), current task if any, spend this month, budget. From
`GET /api/companies/{id}/agents`.
- **runs** — the company's run list, newest first: agent, task, state, what woke it, start,
duration, cost. From `GET /api/companies/{id}/heartbeat-runs` (281 today).
- **log** — the tail of the chosen run, from `GET /api/heartbeat-runs/{runId}/log`. This is
the closest thing to "watching Pi work": the log grows while the run is live, and
`lastOutputAt`/`lastOutputBytes` on the run say how fresh it is.
Actions, all with the user key, all reversible:
- `enter` on a run → its log in the log tab.
- `p` pause, `P` resume an agent (`POST /api/agents/{id}/pause|resume`).
- `w` wake an agent (`POST /api/agents/{id}/wakeup`).
- `e` open its settings (see below).
## Missing: working on an agent — role, model, cost
Paperclip owns these, so the interface edits Paperclip, never a local file. One more panel,
`Agents>settings`, or a tab inside `Agents>agents`:
| Field | Paperclip field | Endpoint |
|---|---|---|
| name and job title | `name`, `title` | `PATCH /api/agents/{id}` |
| role | `role` | same |
| who it reports to | `reportsTo` | same |
| model (the LLM) | `adapterConfig.model` | same, with `replaceAdapterConfig` care |
| heartbeat on/off, concurrency | `runtimeConfig.heartbeat` | same |
| monthly budget | `budgetMonthlyCents` | same |
| pause / resume | `POST /api/agents/{id}/pause`, `/resume` | separate calls |
Two rules that fall out of this and are not negotiable: a change to a model applies to that
agent's **next** run, never to one in flight; and a budget change is recorded with who
changed it, because Paperclip's audit trail is the only record we have.
## Tool panels: Lazygit, Hunk, a shell, the review
Both are installed on this machine and I verified the paths:
`/run/current-system/sw/bin/lazygit` and `/etc/profiles/per-user/sam/bin/hunk`.
They are **not** to be reimplemented. The proposal is one key in `Work>work`:
- `t` opens a chooser: `shell · lazygit · hunk · git log · ocr`.
- The chosen tool opens as a pane **in the current workspace**, beside the panel you were
reading, and takes focus.
- `q` or `esc` closes it and focus returns exactly where it was.
- Nothing is created until you ask, so the layout stays as it is today.
This is the answer to "I thought we had Lazygit and Hunk": they are installed, they are not
wired in, and wiring them in is one pane-opener plus a key.
## The review: ocr is installed and has already run
`ocr` is at `/home/sam/.local/bin/ocr`, version `open-code-review v1.13.6`. It is in the
project's `bin/merge` and `bin/review`, and it has blocked a real merge: on `AUD-165` it
reported a critical finding (`rm -rf "$HOME/tmp"` deleting `/tmp` when `HOME` is empty) and
the merge was refused. The Repair screen once said "not on the path" because a pane started
by TUIOS gets a smaller PATH than a shell; that was fixed by looking in the usual places.
Where it belongs in the interface: a **review panel inside `Work>work`**, showing the last
verdict for the chosen task, its findings, and the blocking ones in red. The merge stays a
command (`bin/merge`), because a merge is a decision with a lock, not a keystroke.
## Mouse-1 and Switch
Measured just now: **not installed** — no `mouse`, `mouse1` or `switch` binary on this
machine, and nothing obviously serving them locally. So nothing can be built on them yet
without adding software first.
Where they would fit if added: Mouse-1 is a small probability model that answers typed
questions; Switch turns a state into those questions. The honest use in this factory is
**routing and judgement that must be cheap and repeatable** — "is this task ready", "does
this comment answer the question", "which agent should take this" — rather than prose
generation. That is a later milestone, after agents and the review panel, and it needs a
decision about installing both, plus a measurement of whether they are better than the
existing cheap model already configured for agents (`omni/deepseek/deepseek-v4-flash`).
## The order I propose
1. **Agents workspace** — roster, runs, log. This is the missing view of Pi working, and it
is read-only, so it cannot break anything.
2. **Agent settings** — role, model, budget, pause/resume. First write surface aimed at
agents rather than tasks.
3. **Tool panels** — `t` opens Lazygit, Hunk, a shell, `ocr` in the current workspace.
4. **Review panel** in `Work>work` — the last verdict and its findings, beside the diff.
5. **Mouse-1 / Switch** — only after the above, and only after they are installed and
measured.
Each of the first four is one pane, one program, no change to the working layout. Nothing in
this list requires splitting workspaces again.
---
# Answers to the open questions, 2026-11-10
**Where settings are set.** `Agents>agents` has a fourth tab, **settings**, which edits the
chosen agent: role, job title, model, monthly budget, heartbeat. `enter` edits the field in
place, `esc` cancels, and `p` / `P` / `w` pause, resume and wake. Each save is one
`PATCH /api/agents/{id}` merged into that agent's existing configuration, so a model change
never wipes its folder or instructions. `Project>project` shows spend and limits read-only,
because Paperclip owns them and a second editable copy is how two screens start disagreeing.
**Spend.** Read from `cost-summary` per task (`Project>costs`, `Board>board`,
`Work>work`). The monthly budget is per agent, set in `Agents>agents` settings. There is no
company-level control on any screen yet.
**Skills for sub-agents.** Not in the interface. Paperclip has the endpoints —
`GET /api/agents/{id}/skills`, `POST /api/agents/{id}/skills/sync` with `add`, `remove` or
`replace` — so a read-only skills row is cheap and a full editor is a later step. The rule
worth keeping: an agent gets the skills its job needs and no more, which the `add`/`replace`
model already expresses.
**Where work is added.** `New task>new`, which writes a Paperclip task. A second place to
write tasks (Tuxedo, a notes file, a card board) would be a second owner for the same fact
and is exactly the failure the old project had. Personal notes are different from tasks: if
they are wanted, they belong in the project's own `.harness/NOTES.md`, and Tuxedo can read
it per project without pretending to be a task board.
**`office --host` from another machine.** Not implemented, and the runbook says why:
session creation needs a real terminal, so a person creates the session
(`tuios new --host nixos-desktop "new test project"`) and then runs `bin/office <folder>`
inside it, on the machine that owns the folder. `--host` today only records the host in the
project file. Driving `bin/office` on `.27` from `.13` would need ssh plus a remote
`bin/board`, which is a deliberate later step, not an accident.
**Seeing and switching projects.** Not built. Paperclip has the list
(`GET /api/companies/{id}/projects`); what it cannot know is which folder on which machine
each one lives in. The proposal: a small local registry of adopted projects
(`~/.config/paperclip-harness/projects.toml`: project id, folder, host, session), filled by
`bin/office`, and a `Project>projects` tab that lists them and switches by writing the
project into the current folder's `.harness/harness.toml`. A project on another machine
can only be switched to if the pane can reach it, so it would be shown as `on .27`, not
offered.
**Artifacts.** Paperclip work products: an uploaded file, a pull request, a preview URL, a
commit or a branch, attached to a task by an agent. They are inspectable records of what a
task produced. Nothing shows them yet; the natural home is a `Work>artifacts` tab listing
them with their links, read from the task's work products.
**"Workspaces" in Paperclip.** Paperclip has no workspaces. It has companies, projects,
issues (tasks), agents and runs. A *workspace* is TUIOS's word for a page; a *pane* is a
terminal inside it. When Paperclip says workspace it means a project's code folder
(`currentExecutionWorkspace`), which is the git worktree a task runs in.
**Charts.** TUIOS ships `plot` (Go), `loom` and TML. Go is the better fit here because the
panels are already Go and the data is already in memory: a spend-by-day chart in
`Project>costs` is a few dozen lines against `tuios.dev/plot`, where a TML surface would be
a second language and a second round trip. Worth doing once the functional tests pass.
**Mouse-1 and Switch.** Still not installed on this machine. Unchanged from the earlier
note: they would serve cheap repeatable judgement, later, after they are installed and
measured.
---
# Findings for the next round of decisions, 2026-11-10
Everything below was measured on this machine. Nothing here is proposed without saying so.
## Skills per agent — the API already models it
`GET /api/agents/{id}/skills` returns, for each agent:
```
mode: "persistent"
desiredSkills: ["paperclipai/paperclip/paperclip"]
entries: [
{ key: "company/<id>/obsidian-cli", runtimeName: "obsidian-cli--b74ae42966",
desired: false, managed: true, state: "stale",
sourcePath: "…/instances/default/skills/<id>/obsidian-cli",
targetPath: "/home/sam/.pi/agent/skills/obsidian-cli--b74ae42966" }
]
```
So Paperclip already models exactly what is wanted: a desired set per agent, a managed set
actually installed, and a state (`stale` today for `obsidian-cli` on `Builder`, which is
worth fixing on its own). `POST /api/agents/{id}/skills/sync` with `add`, `remove` or
`replace` sets the desired set; `replace` is the operation that means "this agent gets these
and nothing else", which is how a sub-agent's context is kept small.
Company level, there is more: `GET/POST /api/companies/{id}/skills` (the catalog),
`/skills/{id}/versions`, `/skills/{id}/audit`, and `GET/PUT /api/companies/{id}/skill-policy`
with `/skill-policy/evaluate`, which is a company-wide rule about which skills may be used.
Proposed in the interface: `Agents>agents` settings tab gains two rows — **skills** (the
desired set, read-only at first, with `add`/`remove` later) and **skill source** (company
catalog or the agent's own). A full skill browser is a later screen, not a panel.
## Memory — three different things wearing one word
| What | State here | What it does |
|---|---|---|
| **engram** | installed, `engram v1.16.1`, MCP server mode, no pi MCP entry yet | persistent memory for agents, over HTTP or MCP |
| **headroom** | running, `headroom-proxy v0.24.0`, healthy at `192.168.20.13:8787`, memory backend **disabled**, compression enabled | compresses prompts on the way out; it is a proxy, not a store |
| **pi memctx** | in use in this session | the memory gateway that injects notes into a prompt |
They are not alternatives: engram stores, headroom compresses, memctx injects. For agents,
the only lever Paperclip gives us is per-agent configuration — `adapterConfig` (the folder,
the model, the instructions bundle) and `runtimeConfig` — so memory is set per agent by
pointing that agent at the proxy or the storage it should use, not by a global switch.
Proposed: a **memory** row in `Agents>agents` settings showing which of the three that agent
uses, read from its adapter configuration; and nothing more until one agent has been run
both ways and the difference measured.
## MOUSE-1
- `MOUSE_API_KEY` **is** set in `~/.config/environment.d/10-secrets.conf` (name checked, value
never printed). `JEV_API_KEY` is set as well.
- No `mouse`, `mouse1`, `jev` or `switch` binary is installed.
- The previous project already wrote the policy, in
`~/paseo/projects/tuios_coding_harness/plan/*`: MOUSE-1 is **"triage only: label, score,
route, escalate. Never a gate, never a permit"**, and explicitly ruled out for deciding a
merge or allowing a tool call, because a cheap classifier that fails open on a network
error turns a green run into a lie.
That policy is right and should be adopted unchanged. Its place in this interface, if it is
added: `Board>board` needs-you could be ordered by a MOUSE-1 score, and `Work>review` could
use it to pre-sort findings. Never the merge, never an approval.
## Lazygit
Already wired: `t` in `Board>board` or `Work>work`, then choose Lazygit. It opens beside the
pane, in the same workspace, and `q` closes it. Hunk, Yazi, Neovim, fzf, a shell and ocr are
in the same chooser.
## The old `office` command, and what ours is missing
The previous project's `office` does four things, in order:
1. makes the folder and remembers the host in `<folder>/.harness/host`;
2. runs `new-project` to write the project scaffold — `settings.toml`, one file per role,
`verify.sh`, the per-screen notes — **writing only what is missing, never overwriting**;
3. refuses to continue if the TUIOS session does not exist, and prints the exact
`tuios new <session>` line to run;
4. runs its `board` to build or repair the pages, then prints how to attach.
Ours does 1, 3 and 4 (with a different file: `.harness/harness.toml`). It does **not** do 2:
there is no scaffold, no per-role file, no `verify.sh`, no page notes. That is a real gap and
the cheapest useful one to close.
## Dashboard — the API already answers it
`GET /api/companies/{id}/dashboard` returns 1.8 KB:
```
agents: {active: 6, running: 0, paused: 0, error: 0}
tasks: {open: 8, inProgress: 0, blocked: 4, done: 147}
costs: {monthSpendCents: 11, monthBudgetCents: 0, monthUtilizationPercent: 0}
pendingApprovals: 0
budgets: {activeIncidents: 0, pendingApprovals: 0, pausedAgents: 0, pausedProjects: 0}
runActivity: [14 entries]
```
That is a whole dashboard in one call, and it is cheap enough to poll. Proposal: a
`Project>overview` panel showing those four blocks and a sparkline of `runActivity`, drawn in
Go with `tuios.dev/plot` rather than embedding Paperclip's web dashboard.
## Not doing
Artifacts, as instructed. Nothing about them will be added.
---
# Pi agents: how their configuration actually resolves
Advice only — nothing here was built. Every fact below was read from the installed code, the
config files, or the API on this machine.
## Your picker, and what it really writes
`pi-config` is an extension with five commands: `/config-add`, `/config-remove`,
`/config-list`, `/config-show`, `/config-setup`. `picker.ts` is the overlay — a filtered list
of `~/.agents/skills` and `~/.agents/extensions` showing which are active in the project and
which are active globally. The commands write **JSON**, at:
```
<project>/.pi/settings.json ← project settings (the picker's "-l" mode)
~/.pi/agent/settings.json ← global settings
```
and the shape they manipulate is a `packages` array of sources and filters:
```json
{"packages": [
"npm:pi-memctx",
{"source": "git:https://gitea.lab.audasmedia.com.au/sam/pi-config",
"extensions": ["extensions/pi-config/index.ts", "extensions/tavily-search/index.ts"]},
{"source": "/home/sam/.agents", "skills": ["nixos-workflow"], "extensions": []}
]}
```
So yes: this is a stable JSON contract, and anything that can write a file can formulate an
agent's context the way the picker does. The question is not *whether* it can be written, but
*which one Pi actually reads on a Paperclip run*.
## What Pi reads, in order
From Pi's own documentation:
- global: `~/.pi/agent/skills/`, `~/.agents/skills/`
- **project, only after the project is trusted**: `.pi/skills/`, `.agents/skills/` in the
working directory and its ancestors up to the git root
- packages and the settings `skills` array
- command line: `--skill <path>`, additive even with `--no-skills`
Settings merge the same way: user settings, then `<cwd>/.pi/settings.json`.
## What Paperclip actually runs
From `@paperclipai/adapter-pi-local`, a run is:
```
pi --mode json
--append-system-prompt <rendered system prompt>
--provider <p> --model <m> --thinking <t>
--tools read,bash,edit,write,grep,find,ls
--session /home/sam/.pi/paperclips/<timestamp>-<agentId>.jsonl
--skill <a directory Paperclip builds from that agent's desired skills>
```
and before the run it symlinks the agent's desired skills into `~/.pi/agent/skills/<runtimeName>`.
Four consequences, and they matter:
1. **Skills are already per agent** — `desiredSkills` plus `POST /api/agents/{id}/skills/sync`.
That is the supported lever, and it is what the `--skill` directory is built from.
2. **The isolation is not complete.** Injected skills are symlinked into the *shared* global
directory and are not removed when another agent runs, and Paperclip does not pass
`--no-skills`. So a skill an agent once had can remain visible to another agent.
3. **The tool list is fixed** at `read,bash,edit,write,grep,find,ls`. An agent cannot gain a
tool by being given an extension unless the adapter is changed.
4. **Project-local `.pi/settings.json` is unlikely to be honoured for Paperclip runs.** Pi only
loads project settings from a trusted path. `~/.pi/agent/trust.json` holds 14 entries and none
of them is a Paperclip worktree or `~/paperclip-agents`. Worse, a worktree path changes with
every task (`…/worktrees/AUD-165-…`), so there is nothing stable to trust.
## So what is the honest advice
- **Agent identity, instructions, model, budget, and skills: Paperclip.** Those are per-agent
already and they are what a run uses. The instructions bundle at
`~/.paperclip/instances/default/companies/<co>/agents/<id>/instructions/AGENTS.md` is the
real per-agent context control, and it is editable through the API.
- **Extensions: one global set, not per agent.** There is no per-agent extension mechanism in
this adapter. Pretending otherwise would produce agents whose behaviour depends on which one
ran last.
- **Folder-level cleverness: keep out of the critical path.** Writing `.pi/settings.json` into a
project is useful for *you* when you open Pi in that folder by hand; it is not reliable
inside a Paperclip run, because of trust and because worktrees are new paths each time.
- **If per-agent extensions are genuinely wanted**, the change is in the adapter: pass a
per-agent config directory or `--no-skills` plus an explicit set, and remove stale links.
That is a Paperclip feature, not something our terminal can arrange from outside.
## History: yes, and it is richer than the run log
- `~/.pi/paperclips/*.jsonl` — **178 files** on this machine, one per run, named
`<timestamp>-<agentId>.jsonl`, and each is the full conversation (session start, model
changes, messages).
- `GET /api/agents/{id}/task-sessions` — maps a task to its session file and working directory.
- `GET /api/heartbeat-runs/{runId}/log` — the run log already shown in `Agents>agents`.
- `GET /api/agents/{id}/runtime-state` — the agent's current runtime state.
A history panel is therefore feasible without new plumbing: list an agent's sessions from
`task-sessions`, open one, and render the JSONL transcript. That is the natural next panel
after the functional tests, and it is read-only.
---
# Correction: per-agent skills are the design, not an accident
The section above gives the wrong impression. Here is the accurate version.
## Each agent has its own skill set, and that is stored in Paperclip
`GET /api/agents/{id}/skills` returns, per agent, `desiredSkills` and the entries actually
managed for it. `Builder` had its own list; `Documenter` has another. Setting it is one call:
```
POST /api/agents/{id}/skills/sync
{ "mode": "replace" | "add" | "remove",
"desiredSkills": ["company/<id>/nixos-workflow", "paperclipai/paperclip/paperclip"] }
```
`replace` means "this agent gets exactly these". At run time the adapter builds a directory
from **that agent's** desired set and passes it as `--skill`, and symlinks those skills into
`~/.pi/agent/skills/<runtimeName>` so the run can find them. So: different agents, different
skills, no problem — this is already the supported path and it needs no picker.
The caveat I over-stated is narrow: the symlinks stay in the shared global directory after a
run, and Paperclip does not pass `--no-skills`, so a skill an agent used once can remain
discoverable by another agent afterwards. That is a leak to know about (and a good argument
for `replace` plus a periodic clean), not "one set for everybody".
Where skills come from: `GET /api/skills/available`, the company list at
`GET /api/companies/{id}/skills`, and the catalog at `GET /api/skills/catalog`. A skill there
can be assigned to any agent with the call above.
## Extensions per agent: yes, through the command
The adapter's configuration document says the agent's command defaults to `pi` and can be set
per agent:
```
adapterConfig.command defaults to "pi"
adapterConfig.env KEY=VALUE pairs
```
So an agent can be given its own wrapper:
```
~/paperclip-agents/builder/pi-wrapper.sh:
#!/bin/sh
exec pi --extension npm:pi-memctx --extension git:https://gitea…/pi-config/extensions/tavily-search/index.ts "$@"
```
and `PATCH /api/agents/{id}` with `{"adapterConfig": {…, "command": "…/pi-wrapper.sh"}}`. The
adapter appends its own arguments (`--mode json`, `--session`, `--skill`, `--tools`,
`--model`) to whatever the command is, so the wrapper is additive and cannot break the run.
`env` is the same lever for environment variables.
That is how per-agent extensions become real today: one wrapper per agent role (`builder`,
`documenter`, `reviewer`), each listing the extensions that role may use. It also gives a
place to set `--no-skills` if strict isolation is wanted, and a place to keep the tool list
deliberately small.
## History: three options, in order of fidelity
1. **Open the real Pi session in a pane.** Paperclip writes every run to
`~/.pi/paperclips/<timestamp>-<agentId>.jsonl` and starts Pi with `--session <file>`. A
pane running `pi --session <that file>` gives the actual conversation, with Pi's own
interface, and can be continued. Highest fidelity, zero new code — a tool-pane entry that
takes a file path. Two writers on one session file is the risk, so this is for sessions
that are finished, or for a deliberate hand-over.
2. **A read-only transcript panel.** Parse the JSONL and draw the messages in `Agents>agents`
as a fourth tab. Integrated, safe, no second process; less faithful than the real thing.
3. **`pi --resume`** in a tool pane, and pick the session from Pi's own list. Simplest of all,
but it makes you find the session by eye.
`GET /api/agents/{id}/task-sessions` maps task → session file and working directory, so option
1 and 2 both have a reliable index and neither needs guessing.
---
# Confirmed: what the picker emulation needs, and what it can actually do
Researched by doing it, on this machine, with the board key. Everything below returned 200 or
201 unless it says otherwise.
## The gap, measured
| Set | Where | Count |
|---|---|---|
| what the pi-config picker lists | `~/.agents/skills` | **21** (archify, caveman, nixos-workflow, tuios, …) |
| what Paperclip can assign | `GET /api/companies/{id}/skills` | **9** (agentmail, obsidian-cli, paperclip, project-docs, system-architect, …) |
So eleven skills a person can see in the picker cannot be given to a Paperclip agent today,
because Paperclip has never heard of them. That is the whole problem, and it is smaller than
it looked.
## What works, in order
1. **Copy the skill into the company's managed directory.** A local import is refused from
anywhere else — `POST …/skills/import` with a path under `~/.agents` returned **403
`skill_workspace_boundary_denied`**, "Import from a configured Paperclip workspace or the
company managed-skill directory". Copying to
`~/.paperclip/instances/default/skills/<companyId>/<name>` and importing that path returned
**201**:
```
{"key": "local/9a9eba3826/nixos-workflow", "slug": "nixos-workflow", …}
```
2. **Assign it to one agent, and only that agent**:
```
POST /api/agents/{agentId}/skills/sync
{"mode": "add", "desiredSkills": ["local/9a9eba3826/nixos-workflow"]} → 200
```
Verified on `Documenter`: its list went from three skills to four and back to three after a
`remove`. Different agents, different lists — as expected.
3. **Extensions have no Paperclip equivalent.** `~/.agents/extensions` holds them; Paperclip's
skill model does not. The per-agent lever is `adapterConfig.command`, which defaults to `pi`
and is appended to by the adapter. A wrapper per agent that passes `--extension …` and then
`exec pi "$@"` is the equivalent of `pi-config` for a Paperclip run.
## So the emulation is this, and nothing more
A picker that does what `pi-config`'s does, for Paperclip agents:
- **reads** `~/.agents/skills` and `~/.agents/extensions` (the same source the picker uses),
the company skill list, and the chosen agent's `desiredSkills`;
- **marks** each entry: available but not in Paperclip · in Paperclip · on this agent;
- **on choosing** an unavailable skill: copy it into the managed directory, import it, then
`skills/sync` it onto that agent;
- **on removing**: `skills/sync` with `remove`;
- **for an extension**: add it to that agent's wrapper file and point
`adapterConfig.command` at the wrapper.
That is a small command or one panel, not a new subsystem, and every step above is already
verified.
## A change made while checking this
`nixos-workflow` was copied into the company's managed directory and imported, so it is now in
Paperclip's list and can be assigned to any agent. It is assigned to nobody. Nothing else was
changed; `Documenter`'s skills were put back exactly as they were.
## Pi history — the cheap answer is probably enough
The instinct is right. Every run is already a file:
```
~/.pi/paperclips/<timestamp>-<agentId>.jsonl 178 of them here
```
`GET /api/agents/{id}/task-sessions` maps a task to its session file and folder. So the
cheapest useful thing is a tool-pane entry that opens `~/.pi/paperclips` in Yazi, or a pane
running `pi --resume` that lists sessions and opens one with Pi's own interface. Both are
minutes of work and neither invents anything.
A transcript tab inside `Agents>agents` is the integration option: read the JSONL, draw the
messages, no second process. Worth doing only if the file browser feels like a detour after a
week of use. Recommendation: do the cheap one now, decide later.
## verify.sh, plainly
`.harness/verify.sh` is a shell script in the project folder. Its only job is to answer one
question: **is this project's work finished?** It exits 0 when the answer is yes.
- It is **not** Paperclip's. Paperclip has no such concept; an agent sets a task's status.
- It is **our** convention, from the old project, and `bin/office` now writes a template:
```sh
#!/bin/sh
echo "no verify command agreed for this project"
exit 0
```
As it stands it says yes to everything, which is honest: there is no agreed check. Replace
the body with the project's real tests when they exist:
```sh
exec go test ./...
```
- `bin/merge` runs it in the task's worktree, and again on the base branch after merging.
- A task may carry its own command in its description; that one wins for that task only.
So: a per-project default, a per-task override, both optional. If neither exists, the merge
says so and lets the `ocr` review decide.
## Headroom, and whether it is good for a project
Headroom is a **compression proxy**: requests pass through it and long prompts are shortened
before they reach the model. It is running here at `192.168.20.13:8787` (v0.24.0, healthy,
memory backend disabled, compression on).
It is good where prompts are long and repetitive — a big repository, a long thread, many tool
results. It is pointless for a small task whose prompt is already short, and it adds a hop and a
failure mode.
The lever is per **agent**, not per project, because a Paperclip agent's provider is configured
on the agent. `adapterConfig.env` accepts variables, so an agent can be pointed at the proxy
without touching anything else. That is why the interface offers a read-out first: until one
agent has run both ways and you have compared cost and quality, "good for some projects" stays
a hypothesis rather than a setting.
## Mouse-1, corrected
Earlier notes in this file said "software is not installed". That was wrong: Mouse-1 is a
**hosted API**, not a program. What exists here is `MOUSE_API_KEY`; what is missing is a client
integration — an HTTP call like any other. No install is needed, and nothing has to run locally.
The old project's policy still applies: triage only, never a gate, never a permit.