# Facts about this machine, checked 2026-11-09 Everything here was measured with a command. Anything not measured is marked unproven. ## Paperclip - Running locally: `http://127.0.0.1:3100`. Public UI: `https://paperclip.lab.audasmedia.com.au`. - Company: `d4921013-e5da-4d88-8016-57d5746e2d03` - Agents, all running the Pi adapter (`pi_local`), all idle: - `Hub` — `108c6220-c9e1-4b62-9e0a-6a763e0d28fd` (this is the identity of `PAPERCLIP_API_KEY`) - `Builder` — `403de1f7-645c-4d35-bc7e-19b01d77bacc` - `Chief of Staff` — `232f1f9e-bac0-4185-8a0e-eddbead33e04` - `Documenter` — `706ffc88-120f-46c4-a5d9-ffa12e2ad5f0` - `researcher` — `a6a2c0ee-570e-4f89-a977-83136b534a2f` - `System` — `fe7a1881-1df9-481b-af74-87292fef288c` - Writes need the user key: `PAPERCLIP_BOARD_KEY`. The agent key `PAPERCLIP_API_KEY` is refused with 403 outside a heartbeat run. - Blockers live at `GET /api/issues/{id}/diagnostics/blockers`, **not** on the task list. ## The sandbox project - `spike-2026-11-09` is **archived**: status `cancelled`, renamed `spike-2026-11-09 (deleted)`, id `9e3f61f6-cce9-49b1-8083-3130345a04a5`. - `DELETE /api/projects/{id}` returned 500, so it was archived instead. Its two tasks (`AUD-157`, `AUD-158`) remain inside the archived project. **Deleting an archived project is still untested.** - Its scratch repo `/tmp/pc-sandbox` was removed. - To make a fresh sandbox, use the payload recorded in `PAPERCLIP-API-NOTES.md`: `POST /api/companies/{companyId}/projects` with `name`, `status: "backlog"` and `workspace: { name, sourceType: "git_repo", cwd, isPrimary: true }`. ## Does a heartbeat wake an agent? PROVEN, 2026-11-10 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`. Measured on `spike-2026-11-10` / `AUD-159`: ``` 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 ``` 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. `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: switched on 2026-11-10, and measured working The instance first had them off. With the flags off, the project's request for a git worktree per task was silently ignored: `AUD-159` worked in `/tmp/pc-sandbox-2026-11-10` on `main`, and the task's `executionWorkspaceId` read `null`. Then, with the person's approval, two experimental settings were switched on: ``` PATCH /api/instance/settings/experimental {"enableIsolatedWorkspaces": true, "enableWorktreeRunExecution": true} → 200 ``` `enableIsolatedWorkspacesByDefault` was deliberately **left off**. That flag would give every project without a policy of its own a worktree, which would change the other nine projects on this instance. Only projects that ask for one now get one. The next task (`AUD-160`) made a real worktree, and Paperclip posted a "Workspace Ready" comment on the task naming it: ``` strategy git_worktree branch AUD-160-milestone-1-t2-read-hello-txt cwd /tmp/pc-sandbox-2026-11-10/.paperclip/worktrees/AUD-160-milestone-1-t2-read-hello-txt baseRef main deliveryState merged_by_ancestry (no commit was needed; the branch equals main) ``` `git worktree list` in the sandbox repository shows that path on that branch. So the worktree is Paperclip's job after all, once the instance allows it, and the terminal program never needs to run `git worktree` itself. ## Fonts, 2026-11-10 Measured, because the answer was "I thought I had installed them": - No Nerd Font was installed (`fc-list | grep -i nerd` returned nothing) and no JetBrains Mono either. Ghostty ships a plain JetBrains Mono of its own, which is why a terminal can look right while every icon renders as a box. - Starship is **not installed**, and there is no `~/.config/starship.toml`. - TUIOS has four glyph sets — `ascii`, `default`, `heavy`, `unicode` — and `default` is active, which is ordinary Unicode, not Nerd Font icons. - TUIOS has 30 themes including six Catppuccin variants, and **no look is applied**: `tuios look show` says "No look is applied". The session is not on Catppuccin. What was changed: - `fonts.packages = [ nerd-fonts.jetbrains-mono ]` and `fonts.fontconfig.enable = true` in `/etc/nixos/configuration.nix`, so every machine built from this flake gets it: `.13`, `.27`, `.51`. `nix build --dry-run` evaluates and wants `nerd-fonts-jetbrains-mono-3.4.0+2.304`. **The rebuild is still to be run** (`sudo` needs a password here). - `~/.config/ghostty/config` sets `font-family = "JetBrainsMono Nerd Font"`. - Rebuilt by the person on 2026-11-10: `fc-list | grep -ci nerd` now returns 96, and `JetBrainsMonoNerdFont-Regular.ttf` is present in the store. The font is installed on `.13`; `.27` and `.51` get it from the same flake on their next rebuild. - The theme is now set: `theme = 'catppuccin_mocha'` in `~/.config/tuios/config.toml`. `tuios set-config` refused for the running session ("would change this machine, not the session"), so the running session keeps its old chrome until it is restarted. ## Toolchain - **Go is installed** (2026-11-10), by `nix profile install nixpkgs#go` → `go1.26.5` at `/home/sam/.nix-profile/bin/go`. That is the quick path: it works now, and it is not part of the system closure. - The durable path is also prepared: `go` and `gopls` were added to `environment.systemPackages` in `/etc/nixos/configuration.nix`. `nix flake check` passes and the dry-run build evaluates. **The rebuild has not been run**, because `sudo` needs a password here. One command finishes it: `sudo nixos-rebuild switch --flake /etc/nixos#nixos-desktop`. Until then the profile copy is what runs. ## The new repository - Local git repo only, one commit, no remote. It is not on Gitea. Push it somewhere before relying on it, or accept that only this machine has it. ## NixOS alignment for Pi, measured 2026-11-12 Goal: the same Pi on all three machines. `pi-coding-agent` comes from nixpkgs, so aligning used to mean bumping the whole system on each machine. That is what broke `.13`: `nixos-rebuild switch` restarts every changed unit in place, the network units restarted, the default route did not come back, and every service that needs the network died with it. Exit code 4 meant "units failed to start", not "the build failed" - the generation had already switched. **The rule that follows: on a machine someone is using, a nixpkgs-sized change goes in with `nixos-rebuild boot` and a reboot, never `switch`.** ### What was done instead, and why it is safe Pi is one package. Instead of moving a whole system to a new nixpkgs, a second flake input pinned to one revision provides only that package: ```nix nixpkgs-pi.url = "github:NixOS/nixpkgs/f4a03c008967841f2a0b3cb7c436fc6fc31671f6"; # in the let block piPkgs = import nixpkgs-pi { inherit system; config.allowUnfree = true; }; # in home-manager users.${user}.home.packages piPkgs.pi-coding-agent ``` and `pi-coding-agent` was removed from `home/sam/home.nix` so exactly one `pi` is on `PATH`. The system pin on those machines did not move, so nothing else in the system changes and no network or service unit is restarted. Remove the input once the system pins are aligned for real. ### State, verified by building, not switching | machine | method | pi in the built system | store path | |---|---|---|---| | `.13` nixos-desktop | full nixpkgs bump (2026-11-07), **running** | 1.5.0 | `4yxlfj0sw5hlk7x1hiv0igm0dckg8rv9` | | `.27` sam-4screen-desktop | `nixpkgs-pi` input, built, **not yet switched** | 1.5.0 | `4yxlfj0sw5hlk7x1hiv0igm0dckg8rv9` | | `.51` sam-thinkpad | `nixpkgs-pi` input, built, **not yet switched** | 1.5.0 | `4yxlfj0sw5hlk7x1hiv0igm0dckg8rv9` | One store path on all three: the same bytes of Pi, not merely the same version number. ### Two other things measured on the way - `programs.zsh.initExtra` is `initContent` in home-manager 2026-11. Both names evaluate on the older home-manager, so the rename is safe in either direction. - **lan-mouse 0.11.0 panics** when `LIBGIT2_NO_VENDOR` is set and no system libgit2 exists; nixpkgs 2026-11-07 sets it. An overlay on `pkgs.lan-mouse` does **not** help. Building the flake's own package with the variable off does: ```nix lm = lan-mouse.packages.${system}.default.overrideAttrs (o: { LIBGIT2_NO_VENDOR = "0"; }); ``` `.13` now lists `lm` in `systemPackages` instead of `lan-mouse`. - `.27`'s full-system bump failed in `home-manager.users.sam.fonts.fontconfig.configFile.fonts.settings` (`recursiveUpdateUntil` / `head values`). The `nixpkgs-pi` route avoids it entirely; it is still an open item for the day the system pins are aligned.