office: worktrees when the folder is a repository, ask before making one, refuse a folder agents cannot see

This commit is contained in:
Sam
2026-11-12 18:09:05 +11:00
parent 55085ba40a
commit f7b77f370f
11 changed files with 1821 additions and 4 deletions

View File

@@ -89,3 +89,82 @@ Committed as `3ec960e`; `docs/NEXT.md` updated with what is done and what remain
- context: [[packs/paperclip-harness/20-context/config|.config]] - context: [[packs/paperclip-harness/20-context/config|.config]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|.config deployment operations]] - runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|.config deployment operations]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|.config repository structure and configuration patterns]] - observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|.config repository structure and configuration patterns]]
---
## Update (2026-11-12 06:19:59)
## Rich discovery update (2026-11-12 06:19:59)
Fixed, and your `security` project is now running.
## What the error was
Two things, and the first hid the second.
**1. The key is not in your shell on `.27`.** `PAPERCLIP_BOARD_KEY` lives in `~/.config/environment.d/10-secrets.conf`, which systemd's user manager reads. On `.13` the session inherits it; on `.27` your terminal is started by the desktop session, which does not import `environment.d`. So the file was right there and the variable was empty.
**2. Even with the key, `office` on `.27` was aiming at the wrong machine.** The default address was `http://127.0.0.1:3100`, and Paperclip runs on `.13`. `.27` can reach it — I checked, 200 — but nothing was asking that address.
Both are fixed in the code, on the Python commands and in the Go program, so it works from a shell and from inside a pane:
- the key, the company id and the API address are read from `~/.config/environment.d/*.conf` when the environment does not have them (read only; never printed);
- if `PAPERCLIP_API_URL` is not set and `127.0.0.1:3100` does not answer, the address becomes `http://192.168.20.13:3100`.
## A third problem your command would have hit
`~/SECURITY` is **not a git repository**, and `--new` always created a project with a branch per task in a worktree. A worktree needs a repository, so every run on that project would have failed. `--new` now checks, and for a folder that is not a repository it creates a `local_path` project: the folder stays in place and a task changes the files directly. It says so when it does that.
## It is created and running
```
project security 8b33e222-8b62-40a2-950d-dbc9a3a2ffcd
source local_path cwd /home/sam/SECURITY
policy none (no worktrees: not a git repository)
session security on .27, 8 panes
panes board · work · agents · reply · new · project · repair = harness
shell = a shell
```
Attach with `tuios attach security` on `.27`.
If you would rather have a branch per task for that folder, `git init` it and re-run `office ~/SECURITY --project security`; the project keeps the folder but you would need a new one to get the worktree policy, because the policy is set when the project is created.
## Two things I noticed on `.27` while doing this
- **`tuios.service` is in `activating (auto-restart)`** — the unit keeps failing to start, while a manually started daemon (pid 1603888) is what your sessions actually live on. That is why your sessions exist at all. After a reboot the manual daemon will not be there, so this is worth fixing before you rely on `.27` surviving a restart.
- The `.27` rebuild for `~/.local/bin` on `PATH` clearly took effect, since `office` resolved by name. **`.51` still needs its rebuild** if you have not done it yet:
```sh
sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad
```
## Committed
`paperclip-harness` → `55085ba`. The updated commands are also copied to `.27` and `.51` at `~/.local/share/paperclip-harness/bin/`, with `--help` working on both.
## Source paths observed
- `paperclip-harness/tui/paperclip`
- `home/sam/SECURITY`
- `HOME/.local/share/paperclip-harness/bin/office`
- `home/sam/SECURITY/.harness/harness.toml`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `etc/profiles/per-user/sam/bin/tuios`
- `home/sam/.config/paperclip-harness/projects.toml`
- `home/sam/SECURITY/.harness/check.sh`
- `home/sam/SECURITY/.harness/NOTES.md`
- `.config/environment.d/10-secrets.conf`
- `.local/share/paperclip-harness/bin`
## Evidence
- Confidence: 84%
- Reason: Update component/repository context with detailed discovered structure and configuration
## Related learned memories <!-- same-turn-2026-11-12 06:19:59 -->
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-config-2026-11-12-06-19-59|Rich discovery: .config 2026-11-12 06:19:59]]
- context: [[packs/paperclip-harness/20-context/config|.config]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|.config repository structure and configuration patterns]]

View File

@@ -1104,3 +1104,185 @@ Backups kept next to each file: `flake.nix.bak-pi2`, `home/sam/home.nix.bak-pi2`
- session: [[packs/paperclip-harness/80-sessions/pi-1-5-0-alignment-across-three-nixos-hosts-via-pinned-nixpk|Pi 1.5.0 alignment across three NixOS hosts via pinned nixpkgs-pi input]] - session: [[packs/paperclip-harness/80-sessions/pi-1-5-0-alignment-across-three-nixos-hosts-via-pinned-nixpk|Pi 1.5.0 alignment across three NixOS hosts via pinned nixpkgs-pi input]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-nixos-2026-11-12-03-29-14|Rich discovery: NixOS 2026-11-12 03:29:14]] - session: [[packs/paperclip-harness/80-sessions/rich-discovery-nixos-2026-11-12-03-29-14|Rich discovery: NixOS 2026-11-12 03:29:14]]
- context: [[packs/paperclip-harness/20-context/home|NixOS]] - context: [[packs/paperclip-harness/20-context/home|NixOS]]
---
## Update (2026-11-12 06:28:29)
## Rich discovery update (2026-11-12 06:28:28)
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environment` from the session start, which most compositors support declaring). This is the "proper" fix and keeps one source of truth, but it is a session-startup change on each machine.
You can check any machine in one line:
```sh
systemctl --user show-environment | grep -c PAPERCLIP # what systemd knows
env | grep -c PAPERCLIP # what your shell knows
```
If the first is non-zero and the second is zero, only the inheritance is missing.
## 4. TUIOS on `.27` — it is one daemon, plus one unit that cannot start
You read it correctly, and here is exactly what is happening.
- A daemon is **running**: PID 1603888, started **4 November at 22:49 from a ghostty terminal**, and it holds your **11 sessions**.
- The systemd user unit `tuios.service` is **enabled and tries to start a daemon every 5 seconds**. It finds the hand-started one and exits with status 1:
```
tuios[226383]: a daemon is already running (PID 1603888). Stop it with 'tuios kill-server' first, or use it as it is
tuios.service: Scheduled restart job, restart counter is at 141.
```
- So there is **one daemon and eleven sessions**, not two of anything. The unit has never succeeded — 141 attempts.
On `.13` the unit is `active` with 0 restarts, which is why the two machines behave differently: there the daemon is supervised, so it comes back after a reboot and TUIOS restores the sessions. On `.27` the daemon is not supervised — it only survives because it detached from that terminal.
**This needs no rebuild.** It needs one decision, at a moment when losing the sessions is acceptable:
```sh
tuios list-sessions # see what you would lose (11 today)
tuios kill-server # stop the hand-started daemon
systemctl --user restart tuios.service
systemctl --user status tuios.service # should be active, restarts 0
```
After that the unit owns the daemon, it starts at login, and the retry loop stops. Do it when you are not in the middle of something on `.27` — the `security` session I just created lives in that daemon and will go away with it.
The rebuilds you still owe are unrelated:
```sh
# .51 only, if you have not done it yet
sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad
```
`.27` is already on the new generation, since `office` resolved by name there.
I have changed nothing since the last commit except the code you already have (`55085ba`). Say which of these you want done and I will do only that.
## Source paths observed
- `run/current-system/sw/bin/bash`
- `run/wrappers/bin`
- `home/sam/.nix-profile/bin`
- `home/sam/.config/systemd/user/tuios.service`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `user.slice/user-1000.slice/session-1233.scope`
- `user.slice/user-1000.slice/user`
- `1000.service/app.slice/app-ghostty-surface-transient-4128995.scope`
- `home/sam/SECURITY`
- `home/sam/paperclip-agents/researcher`
- `home/sam/paperclip-agents/builder`
- `home/sam/paperclip-agents`
- `.local/share/paperclip-harness/bin`
- `folder/.paperclip/worktrees/AUD-172-`
- `folder/.paperclip/worktrees/AUD-173-`
- `.config/environment.d/10-secrets.conf`
## Evidence
- Confidence: 84%
- Reason: Update component/repository context with detailed discovered structure and configuration
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -524,3 +524,28 @@ Alternatives considered: bumping the shared system nixpkgs (rejected — broke `
- session: [[packs/paperclip-harness/80-sessions/pi-1-5-0-alignment-across-three-nixos-hosts-via-pinned-nixpk|Pi 1.5.0 alignment across three NixOS hosts via pinned nixpkgs-pi input]] - session: [[packs/paperclip-harness/80-sessions/pi-1-5-0-alignment-across-three-nixos-hosts-via-pinned-nixpk|Pi 1.5.0 alignment across three NixOS hosts via pinned nixpkgs-pi input]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-nixos-2026-11-12-03-29-14|Rich discovery: NixOS 2026-11-12 03:29:14]] - session: [[packs/paperclip-harness/80-sessions/rich-discovery-nixos-2026-11-12-03-29-14|Rich discovery: NixOS 2026-11-12 03:29:14]]
- context: [[packs/paperclip-harness/20-context/home|NixOS]] - context: [[packs/paperclip-harness/20-context/home|NixOS]]
---
## Update (2026-11-12 06:28:29)
Decision: (a) Keep Paperclip project folders on `.13`, the machine that serves Paperclip and runs pi agents; other machines are for viewing sessions via `office --host`. (b) `--new` now chooses `local_path` projects when the target folder is not a git repository (was previously always worktree, which would fail at first step); worktree remains the right choice for parallel/review/merge. (c) Harness commands and the Go program read the secrets file themselves when PAPERCLIP env vars are absent, so behavior is identical across systemd-launched and desktop-launched terminals.
Context: user asked whether harness should be on other machines; found `/home/Sam/SECURITY` on `.27` broke agent tasks on `.13`; env inheritance differed per machine.
Alternatives considered: second Paperclip on `.27` (deferred, real project); sshfs/NFS mount (rejected as extra moving part that fails quietly); sourcing secrets in `.zshrc` (rejected: leaks secrets into every child); `systemctl --user import-environment` at session start (deferred, per-machine session change).
Consequences: panes and agent work agree on `.13`; non-git folders work in single-task mode but lose diff/review/merge; secrets file read adds a fallback path.
Follow-up: consider making `office` warn/refuse when folder's machine != Paperclip machine; decide the `.27` TUIOS supervision switch separately.
## Evidence
- Confidence: 85%
- Reason: Explicit architectural choices with rationale and alternatives that shape future project setup.
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -5609,3 +5609,501 @@ Backups kept as `home/sam/home.nix.bak-path` on both, if you want to see the exa
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|Rebuild .27 and .51 after adding ~/.local/bin to home-manager PATH]] - runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|Rebuild .27 and .51 after adding ~/.local/bin to home-manager PATH]]
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Added ~/.local/bin to PATH on .27/.51 and committed; .13 untouched]] - action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Added ~/.local/bin to PATH on .27/.51 and committed; .13 untouched]]
- session: [[packs/paperclip-harness/80-sessions/nixos-fleet-path-fix-rollout-27-and-51-need-home-rebuild-13-|NixOS fleet PATH fix rollout: .27 and .51 need home rebuild, .13 already correct]] - session: [[packs/paperclip-harness/80-sessions/nixos-fleet-path-fix-rollout-27-and-51-need-home-rebuild-13-|NixOS fleet PATH fix rollout: .27 and .51 need home rebuild, .13 already correct]]
---
## Update (2026-11-12 06:19:59)
## Structural/configuration observations (2026-11-12 06:19:59)
Fixed, and your `security` project is now running.
## What the error was
Two things, and the first hid the second.
**1. The key is not in your shell on `.27`.** `PAPERCLIP_BOARD_KEY` lives in `~/.config/environment.d/10-secrets.conf`, which systemd's user manager reads. On `.13` the session inherits it; on `.27` your terminal is started by the desktop session, which does not import `environment.d`. So the file was right there and the variable was empty.
**2. Even with the key, `office` on `.27` was aiming at the wrong machine.** The default address was `http://127.0.0.1:3100`, and Paperclip runs on `.13`. `.27` can reach it — I checked, 200 — but nothing was asking that address.
Both are fixed in the code, on the Python commands and in the Go program, so it works from a shell and from inside a pane:
- the key, the company id and the API address are read from `~/.config/environment.d/*.conf` when the environment does not have them (read only; never printed);
- if `PAPERCLIP_API_URL` is not set and `127.0.0.1:3100` does not answer, the address becomes `http://192.168.20.13:3100`.
## A third problem your command would have hit
`~/SECURITY` is **not a git repository**, and `--new` always created a project with a branch per task in a worktree. A worktree needs a repository, so every run on that project would have failed. `--new` now checks, and for a folder that is not a repository it creates a `local_path` project: the folder stays in place and a task changes the files directly. It says so when it does that.
## It is created and running
```
project security 8b33e222-8b62-40a2-950d-dbc9a3a2ffcd
source local_path cwd /home/sam/SECURITY
policy none (no worktrees: not a git repository)
session security on .27, 8 panes
panes board · work · agents · reply · new · project · repair = harness
shell = a shell
```
Attach with `tuios attach security` on `.27`.
If you would rather have a branch per task for that folder, `git init` it and re-run `office ~/SECURITY --project security`; the project keeps the folder but you would need a new one to get the worktree policy, because the policy is set when the project is created.
## Two things I noticed on `.27` while doing this
- **`tuios.service` is in `activating (auto-restart)`** — the unit keeps failing to start, while a manually started daemon (pid 1603888) is what your sessions actually live on. That is why your sessions exist at all. After a reboot the manual daemon will not be there, so this is worth fixing before you rely on `.27` surviving a restart.
- The `.27` rebuild for `~/.local/bin` on `PATH` clearly took effect, since `office` resolved by name. **`.51` still needs its rebuild** if you have not done it yet:
```sh
sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad
```
## Committed
`paperclip-harness` → `55085ba`. The updated commands are also copied to `.27` and `.51` at `~/.local/share/paperclip-harness/bin/`, with `--help` working on both.
## Source paths observed
- `paperclip-harness/tui/paperclip`
- `home/sam/SECURITY`
- `HOME/.local/share/paperclip-harness/bin/office`
- `home/sam/SECURITY/.harness/harness.toml`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `etc/profiles/per-user/sam/bin/tuios`
- `home/sam/.config/paperclip-harness/projects.toml`
- `home/sam/SECURITY/.harness/check.sh`
- `home/sam/SECURITY/.harness/NOTES.md`
- `.config/environment.d/10-secrets.conf`
- `.local/share/paperclip-harness/bin`
## Evidence
- Confidence: 82%
- Reason: Captured factual repo structure/configuration patterns from rich discovery
## Related learned memories <!-- same-turn-2026-11-12 06:19:59 -->
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-config-2026-11-12-06-19-59|Rich discovery: .config 2026-11-12 06:19:59]]
- context: [[packs/paperclip-harness/20-context/config|.config]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|.config repository structure and configuration patterns]]
---
## Update (2026-11-12 06:28:29)
On `.27`, TUIOS is running as a hand-started daemon: PID 1603888, started Wed Nov 4 22:49:36 2026 from a ghostty terminal, cgroup `/user.slice/user-1000.slice/user@1000.service/app.slice/app-ghostty-surface-transient-4128995.scope`, holds 11 sessions. The systemd user unit `~/.config/systemd/user/tuios.service` is enabled and ExecStart=`/nix/store/...-tuios-0.12.1/bin/tuios daemon`; it retries every ~5s and always fails with 'a daemon is already running (PID 1603888)' → status 1, restart counter 141, never succeeded. Sessions currently in that daemon include the `security` session created today. On `.13` the same unit is `active` with 0 restarts — supervised, restores sessions after reboot — which is why the two machines behave differently.
## Detailed observations from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — T
…[truncated]…
## Evidence
- Confidence: 90%
- Reason: Concrete state observation future agents should know before touching tuios on .27.; enriched with final-answer detail because persistence should be reusable
---
## Update (2026-11-12 06:28:29)
Agents run on `.13` inside `/home/sam/paperclip-agents/<name>` (builder cwd `/home/sam/paperclip-agents/builder`, researcher cwd `/home/sam/paperclip-agents/researcher`, Hub cwd None). `/home/sam/SECURITY` exists on `.27` but not on `.13` (`ls /home/sam/SECURITY` on .13 → nothing). A project whose folder is on `.27` shows panes correctly but agent tasks are pointed at a nonexistent path → panes work, work does not. Also: `/home/sam/SECURITY` is not a git repository (`git rev-parse --is-inside-work-tree` → fatal). The `security` Paperclip project was created today as `local_path` (was going to be worktree but folder isn't a repo). Paperclip API reachability from .13 was not confirmed (grep/command exited 1 with 0 matches).
## Detailed observations from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — T
…[truncated]…
## Evidence
- Confidence: 85%
- Reason: Factual cross-machine gap plus repo-state detail that will affect future project creation and agent task runs.; enriched with final-answer detail because persistence should be reusable
---
## Update (2026-11-12 06:28:29)
Vars in `~/.config/environment.d/*.conf` are read by the systemd user manager and inherited by everything it starts (TUIOS service, panes, commands run inside panes). A terminal launched by the desktop session (e.g. ghostty on `.27`) is NOT a child of the user manager and does not read the file. Measured: on `.13`, user manager and shell both have PAPERCLIP vars; on `.27`, user manager has 1, shell has 0 — file is fine, inheritance path is missing. Symptom only appeared on `.27` because previous use had all been on `.13` or inside TUIOS panes. Mitigation chosen: commands read the secrets file directly when the env var is absent (Go program does too). Diagnostic: `systemctl --user show-environment | grep -c PAPERCLIP` vs `env | grep -c PAPERCLIP`.
## Detailed observations from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — T
…[truncated]…
## Evidence
- Confidence: 90%
- Reason: Durable diagnostic and root cause for a real, recurring class of bug; explains user-visible missing-keys surprise.; enriched with final-answer detail because persistence should be reusable
---
## Update (2026-11-12 06:28:29)
## Structural/configuration observations (2026-11-12 06:28:28)
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environme
…[truncated]…
## Source paths observed
- `run/current-system/sw/bin/bash`
- `run/wrappers/bin`
- `home/sam/.nix-profile/bin`
- `home/sam/.config/systemd/user/tuios.service`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `user.slice/user-1000.slice/session-1233.scope`
- `user.slice/user-1000.slice/user`
- `1000.service/app.slice/app-ghostty-surface-transient-4128995.scope`
- `home/sam/SECURITY`
- `home/sam/paperclip-agents/researcher`
- `home/sam/paperclip-agents/builder`
- `home/sam/paperclip-agents`
- `.local/share/paperclip-harness/bin`
- `folder/.paperclip/worktrees/AUD-172-`
- `folder/.paperclip/worktrees/AUD-173-`
- `.config/environment.d/10-secrets.conf`
## Evidence
- Confidence: 82%
- Reason: Captured factual repo structure/configuration patterns from rich discovery
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -1680,3 +1680,427 @@ Troubleshooting: if `office` still isn't found, confirm wrapper exists at ~/.loc
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|Rebuild .27 and .51 after adding ~/.local/bin to home-manager PATH]] - runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|Rebuild .27 and .51 after adding ~/.local/bin to home-manager PATH]]
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Added ~/.local/bin to PATH on .27/.51 and committed; .13 untouched]] - action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Added ~/.local/bin to PATH on .27/.51 and committed; .13 untouched]]
- session: [[packs/paperclip-harness/80-sessions/nixos-fleet-path-fix-rollout-27-and-51-need-home-rebuild-13-|NixOS fleet PATH fix rollout: .27 and .51 need home rebuild, .13 already correct]] - session: [[packs/paperclip-harness/80-sessions/nixos-fleet-path-fix-rollout-27-and-51-need-home-rebuild-13-|NixOS fleet PATH fix rollout: .27 and .51 need home rebuild, .13 already correct]]
---
## Update (2026-11-12 06:28:29)
Trigger: systemd user unit `tuios.service` on `.27` looping `activating (auto-restart)`, status 1, restart counter climbing (141+); a hand-started daemon (PID 1603888) is holding sessions instead.
Prerequisites: user is NOT mid-task on `.27`; accept losing sessions currently in the hand daemon (11 today, including the `security` session). No rebuild required — this is a state/ownership fix, not a code change.
Procedure:
1. `tuios list-sessions` — review what will be lost.
2. `tuios kill-server` — stop the hand-started daemon.
3. `systemctl --user restart tuios.service`.
4. `systemctl --user status tuios.service` — expect `active`, restarts 0.
Result: unit owns the daemon, starts at login, retry loop stops, sessions restore via service.
Rollback/caveat: sessions in the old daemon are gone once killed — recover from TUIOS's own session store if present; otherwise recreate. Unrelated rebuild still outstanding (only if not yet done): `sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad` on `.51`; `.27` already on new generation.
## Detailed procedure from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environment` from the session start, which most compositors support declaring). This is the "proper" fix and keeps one source of truth, but it is a session-startup change on each machine.
You can check any machine in one line:
```sh
systemctl --user show-environment | grep -c PAPERCLIP # what systemd knows
env | grep -c PAPERCLIP # what your shell knows
```
If the first is non-zero and the second is zero, only the inheritance is missing.
## 4. TUIOS on `.27` — it is one daemon, plus one unit that cannot start
You read it correctly, and here is exactly what is happening.
- A daemon is **running**: PID 1603888, started **4 November at 22:49 from a ghostty terminal**, and it holds your **11 sessions**.
- The systemd user unit `tuios.service` is **enabled and tries to start a daemon every 5 seconds**. It finds the hand-started one and exits with status 1:
```
tuios[226383]: a daemon is already running (PID 1603888). Stop it with 'tuios kill-server' first, or
…[truncated]…
## Evidence
- Confidence: 90%
- Reason: Concrete repeatable procedure with explicit safe/dangerous steps and caveats.; enriched with final-answer detail because persistence should be reusable
---
## Update (2026-11-12 06:28:29)
Trigger: want Work>change / Work>review / Work>merge flows and parallel-safe tasks for a folder Paperclip created as `local_path`.
Background: workwork is a git feature; `local_path` projects edit folder files directly (no isolation, diff, or merge). Project policy is fixed at creation, so you cannot edit an existing project's policy — recreate it.
Procedure:
1. `cd ~/SECURITY` (or target folder).
2. `git init -b main`.
3. `git add -A && git commit -m "security notes as they stand"`.
4. Remove/replace the existing `local_path` project; recreate with `--new` so Paperclip assigns the worktree policy.
After creation, tasks get `folder/.paperclip/worktrees/<TASK>` on branch `<TASK>` off `main`.
Prerequisite: folder must live on `.13` (machine serving Paperclip), not `.27`, or agents will point at a nonexistent path.
Fallback for one-task-at-a-time notes folders: keep `local_path` — honest and fine.
## Detailed procedure from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environment` from the session start, which most compositors support declaring). This is the "proper" fix and keeps one source of truth, but it is a session-startup change on each machine.
You can check any machine in one line:
```sh
systemctl --user show-environment | grep -c PAPERCLIP # what systemd knows
env | grep -c PAPERCLIP # what your shell knows
```
If the first is non-zero and the second is zero, only the inheritance is missing.
## 4. TUIOS on `.27` — it is one daemon, plus one unit that cannot start
You read it correctly, and here is exactly what is happening.
- A daemon is **running**: PID 1603888, started **4 November at 22:49 from a ghostty terminal**, and it holds your **11 sessions**.
- The systemd user unit `tuios.service` is **enabled and tries to start a daemon every 5 seconds**. It finds the hand-started one and exits with status 1:
```
tuios[226383]: a daemon is already running (PID 1603888). Stop it with 'tuios kill-server' first, or
…[truncated]…
## Evidence
- Confidence: 85%
- Reason: Actionable procedure specific to this repo/paperclip behavior, derived from a real user issue.; enriched with final-answer detail because persistence should be reusable
---
## Update (2026-11-12 06:28:29)
Trigger: command works in one terminal/machine but not another, or 'keys not in terminal'.
Steps:
1. On the affected machine: `systemctl --user show-environment | grep -c PAPERCLIP` (what systemd's user manager knows) and `env | grep -c PAPERCLIP` (what your shell knows).
2. If first non-zero, second zero: the file was read but not inherited — terminal launched by desktop, not user manager.
3. Confirm shell origin if needed: inspect parent PIDs / cgroup (ssh session scope vs `app-ghostty-*.scope`).
Remediation options (least → most invasive):
- (default, already in code) harness commands + Go program self-read the secrets file when var is missing — preferred, no rebuild.
- add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc` — leaks secrets to every child shell.
- `systemctl --user import-environment` from session/compositor startup — proper single-source fix, per-machine change.
## Detailed procedure from completed turn
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environment` from the session start, which most compositors support declaring). This is the "proper" fix and keeps one source of truth, but it is a session-startup change on each machine.
You can check any machine in one line:
```sh
systemctl --user show-environment | grep -c PAPERCLIP # what systemd knows
env | grep -c PAPERCLIP # what your shell knows
```
If the first is non-zero and the second is zero, only the inheritance is missing.
## 4. TUIOS on `.27` — it is one daemon, plus one unit that cannot start
You read it correctly, and here is exactly what is happening.
- A daemon is **running**: PID 1603888, started **4 November at 22:49 from a ghostty terminal**, and it holds your **11 sessions**.
- The systemd user unit `tuios.service` is **enabled and tries to start a daemon every 5 seconds**. It finds the hand-started one and exits with status 1:
```
tuios[226383]: a daemon is already running (PID 1603888). Stop it with 'tuios kill-server' first, or
…[truncated]…
## Evidence
- Confidence: 85%
- Reason: Compact reusable diagnostic + remediation ladder for a recurring env-inheritance class of issue.; enriched with final-answer detail because persistence should be reusable
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -0,0 +1,69 @@
---
type: session
id: session.paperclip-harness.paperclip-harness-multi-machine-setup-program-delivery-git-w
title: Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…
status: active
source_of_truth: false
freshness: current
last_reviewed: 2026-11-12
tags:
- pack/paperclip-harness
- agent-memory/session
- paperclip-harness
- tuios
- systemd
- environment.d
- git-worktree
- local_path
- multi-machine
- pi-agents
---
# Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…
Discovery from user Q&A about paperclip-harness across machines (.13 = sam-4screen-desktop, .27 = ?, .51 = sam-thinkpad).
Architecture split:
- Harness CODE lives in one place only: `.13`. Single checkout, single history, no drift.
- Harness PROGRAM (built bin/ + Go binary) must be present on every machine that *shows* panes, because a pane runs on the machine owning the folder.
- Paperclip + pi agents live on `.13`. Paperclip spawns `pi` locally in `/home/sam/paperclip-agents/<agent>`.
Program delivery to laptop/other hosts: `office ... --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` and places a short `office` shim on PATH. Copy refreshes on every use, no manual install. DO NOT keep a second editable checkout on the laptop — treat `~/.local/share` copy as delivery-only, not a workspace. To change harness from laptop: clone, edit, push; next `--host` run picks up new build.
Gotcha / limitation: `/home/sam/SECURITY` exists only on `.27`, NOT on `.13`. Paperclip agents run on `.13` (builder cwd=`/home/sam/paperclip-agents/builder`, researcher cwd=`/home/sam/paperclip-agents/researcher`), so a project folder on `.27` shows panes but agent tasks point at a nonexistent `.13` path. Mitigation options ranked: (1) keep project folders on `.13` (simplest, current pattern), (2) keep folder on `.13` and ssh-edit from `.27`, (3) sshfs/NFS mount .27 home onto .13, (4) run second Paperclip on `.27`. Planned guard: make `office` warn or refuse when project folder's machine != machine serving Paperclip.
Git worktrees vs local_path:
- Paperclip worktree projects create per-task dirs `folder/.paperclip/worktrees/<TASK>` on branch `<TASK>` under folder root `main` branch. Enables parallel tasks, diffs (Work>change), review (ocr), Work>merge.
- Worktree is a git feature; a non-git folder (`~/SECURITY` — `git rev-parse --is-inside-work-tree` fails) can't have one.
- `--new` previously requested worktree projects unconditionally → would fail. It now creates a `local_path` project for non-repos: files edited directly, no isolation, no diff, no merge. Fine for single-task notes folders; use a git repo if you need review/merge.
- To upgrade SECURITY: `cd ~/SECURITY && git init -b main && git add -A && git commit -m ...`, then recreate project with `--new` (policy is fixed at creation; existing project can't just be edited).
Environment variable inheritance:
- Env vars belong to the process that started your shell. `~/.config/environment.d/*.conf` is read by systemd's user manager; children (TUIOS, its panes) inherit; a desktop-launched terminal (ghostty from graphical session on `.27`) does NOT, so shell has 0 PAPERCLIP vars while user manager has 1.
- On `.13` terminals are started by TUIOS (systemd user service) so inheritance works; `.27` desktop terminal was the first non-systemd path used → first time noticed. Nothing changed.
- Fixes: (1) implemented — commands and Go program read the secrets file directly when env var missing (works any terminal/machine, no rebuild); (2) add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc` (leaks secrets into every shell's children); (3) `systemctl --user import-environment` at session start (proper fix, per-machine session-startup change).
- Diagnostic one-liners: `systemctl --user show-environment | grep -c PAPERCLIP` (what systemd knows) vs `env | grep -c PAPERCLIP` (what shell knows). Non-zero then zero = only inheritance missing.
TUIOS on `.27`:
- Real daemon PID 1603888 started 4 Nov 22:49 from a ghostty terminal, cgroup `app-ghostty-surface-transient-*.scope`, holds 11 sessions.
- systemd user unit `tuios.service` is enabled and retrying every 5s; fails because daemon already running (`a daemon is already running (PID 1603888)`) → exit status 1, restart counter at 141, never succeeded.
- On `.13` unit is `active`, 0 restarts (supervised; restores sessions after reboot). `.27` daemon only survives because detached from terminal.
- No rebuild needed; needs one decision at a safe moment, procedure under runbook.
## Evidence
- Confidence: 90%
- Reason: Turn contains many durable architecture/architecture-gotcha details spanning delivery model, worktree semantics, env inheritance, and TUIOS supervision state; worth a session snapshot.
## Related
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -0,0 +1,201 @@
---
type: session
id: session.paperclip-harness.rich-discovery-cannot-2026-11-12-06-28-28
title: Rich discovery: cannot 2026-11-12 06:28:28
status: active
source_of_truth: false
freshness: current
last_reviewed: 2026-11-12
tags:
- pack/paperclip-harness
- agent-memory/session
- autolearn
- rich-discovery
- repo/cannot
---
# Rich discovery: cannot 2026-11-12 06:28:28
Sanitized rich discovery snapshot captured automatically from the completed turn.
## Source paths observed
- `run/current-system/sw/bin/bash`
- `run/wrappers/bin`
- `home/sam/.nix-profile/bin`
- `home/sam/.config/systemd/user/tuios.service`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `user.slice/user-1000.slice/session-1233.scope`
- `user.slice/user-1000.slice/user`
- `1000.service/app.slice/app-ghostty-surface-transient-4128995.scope`
- `home/sam/SECURITY`
- `home/sam/paperclip-agents/researcher`
- `home/sam/paperclip-agents/builder`
- `home/sam/paperclip-agents`
- `.local/share/paperclip-harness/bin`
- `folder/.paperclip/worktrees/AUD-172-`
- `folder/.paperclip/worktrees/AUD-173-`
- `.config/environment.d/10-secrets.conf`
## Final answer snapshot
## 1. Should the harness be on the other machines?
There are three separate things, and it helps to keep them apart.
| Thing | Where it must be | Why |
|---|---|---|
| The **code** (this repository) | one place: `.13` | one copy to edit, one history, no drift |
| The **program** (the built commands + the Go binary) | on every machine that must *show* panes | a pane runs on the machine that owns the folder, so the program must be there |
| **Paperclip** and the **agents** | `.13` | Paperclip spawns `pi` locally, in `/home/sam/paperclip-agents/<name>` |
So for the laptop: **yes, a copy of the program — and it already arrives by itself.** `office … --host sam-thinkpad` copies `bin/` to `~/.local/share/paperclip-harness/bin/` there and leaves a short `office` on `PATH`. Nothing to install by hand, and the copy is refreshed on every use, so it cannot drift.
What you should *not* do is keep a second editable checkout on the laptop. If you want to change the harness while sitting on the laptop, clone the repository, edit, push, and the next `--host` run copies the new build out. The copy on `~/.local/share` is a delivery, not a workspace.
**But there is a limit I have to be straight about, and it affects the `security` project I just made.** Agents run on `.13`. `/home/sam/SECURITY` does not exist on `.13`:
```
.13: ls /home/sam/SECURITY → nothing
Builder runs in → /home/sam/paperclip-agents/builder
```
A project whose folder is on `.27` will show its eight panes on `.27` correctly, but when you assign a task, the agent on `.13` is pointed at `/home/sam/SECURITY` and finds nothing there. So the panes work and the work does not. Four ways out, in the order I would consider them:
1. **Keep project folders on `.13`.** Then panes and agents agree, and `--host` is only for looking at a session that lives on another machine. This is the simplest and matches how the system already works.
2. Put the folder on `.13` and reach it from `.27` over ssh when you want to edit by hand.
3. Mount `.27`'s home on `.13` (sshfs or NFS) so Paperclip can see the path. One more moving part, and a mount that disappears breaks the agent quietly.
4. Run a second Paperclip on `.27`. That is a real project, not a flag.
If you want, I can make `office` refuse — or loudly warn — when the folder's machine is not the machine serving Paperclip, which is the check that would have caught this before the project was made.
## 2. The git issue, and what worktrees are actually for
A worktree is how two tasks avoid standing on each other. Paperclip gives each task its own directory and its own branch, taken from the same repository:
```
folder/ main
folder/.paperclip/worktrees/AUD-172-… branch AUD-172-…
folder/.paperclip/worktrees/AUD-173-… branch AUD-173-…
```
Each agent edits its own copy on its own branch. Nothing collides, you can look at the diff, review it, and merge one branch at a time. That is the whole flow your `Work>change`, `Work>review` and `Work>merge` panes are built around.
**A worktree is a git feature. A folder that is not a git repository cannot have one.** `~/SECURITY` is not a repository:
```
$ cd ~/SECURITY && git rev-parse --is-inside-work-tree
fatal: not a git repository
```
Before today, `--new` asked Paperclip for a worktree project anyway, so every run would have failed at the first step. It now creates a `local_path` project instead: the folder stays where it is, and a task edits those files directly.
What that costs you:
| | worktree project | `local_path` project |
|---|---|---|
| two tasks at once | isolated, no collision | can overwrite each other |
| diff to read | yes, `Work>change` | nothing to show |
| review with `ocr` | yes | nothing to review |
| merge | yes, one at a time | nothing to merge; changes are already in the folder |
So: **Pi agents do not need worktrees to run. They need them to run several tasks at once without colliding, and to give you a diff to approve.** For a notes folder where you run one task at a time, `local_path` is honest and fine. For anything you want reviewed and merged, it needs to be a repository.
If you want SECURITY done properly:
```sh
cd ~/SECURITY
git init -b main
git add -A && git commit -m "security notes as they stand"
```
and then create the project again with `--new` so it gets the worktree policy. A project's policy is set when it is created, so the existing `security` project would need replacing rather than editing.
## 3. Why the keys are in one terminal and not another
An environment variable belongs to the **process that started your shell**. Nothing more. So the question is always "who started this terminal", and the answer differs per machine.
**On `.13`:** your terminals are started by TUIOS, and TUIOS's daemon is a systemd user service. When systemd starts your user manager it reads `~/.config/environment.d/*.conf` and keeps those variables. Everything the manager starts inherits them — TUIOS, its panes, and `office` inside them. Measured: the user manager holds the `PAPERCLIP` variables, and so does the shell.
**On `.27`:** your terminal is started by the desktop (ghostty from the graphical session), which is not systemd's user manager, so the file is never read on that path. Measured on `.27`:
```
systemd user manager PAPERCLIP vars : 1 ← the file was read
your shell's PAPERCLIP vars : 0 ← but the shell did not inherit it
```
The file is right there and correct. The shell simply never saw it.
**Why it never bit you before:** you had only ever run these commands on `.13` — the machine that starts terminals through systemd — or inside a TUIOS pane, which is also systemd. `.27` is the first place you typed `office` into a desktop terminal, so it is the first time the difference mattered. Nothing changed; a path you had never taken did.
Three ways to close the gap, and I have already put the least intrusive one in the code:
1. **What I did:** the commands read the secrets file themselves when the variable is missing. Works from any terminal, on any machine, with no rebuild. The Go program does the same, so panes behave too.
2. Explicit, if you prefer shells to carry it: add `source ~/.config/environment.d/10-secrets.conf` to `~/.zshrc`. Simple, but it puts secrets in every shell's environment, including child processes you may not intend.
3. Make the desktop import the user manager's environment at login (`systemctl --user import-environment` from the session start, which most compositors support declaring). This is the "proper" fix and keeps one source of truth, but it is a session-startup change on each machine.
You can check any machine in one line:
```sh
systemctl --user show-environment | grep -c PAPERCLIP # what systemd knows
env | grep -c PAPERCLIP # what your shell knows
```
If the first is non-zero and the second is zero, only the inheritance is missing.
## 4. TUIOS on `.27` — it is one daemon, plus one unit that cannot start
You read it correctly, and here is exactly what is happening.
- A daemon is **running**: PID 1603888, started **4 November at 22:49 from a ghostty terminal**, and it holds your **11 sessions**.
- The systemd user unit `tuios.service` is **enabled and tries to start a daemon every 5 seconds**. It finds the hand-started one and exits with status 1:
```
tuios[226383]: a daemon is already running (PID 1603888). Stop it with 'tuios kill-server' first, or use it as it is
tuios.service: Scheduled restart job, restart counter is at 141.
```
- So there is **one daemon and eleven sessions**, not two of anything. The unit has never succeeded — 141 attempts.
On `.13` the unit is `active` with 0 restarts, which is why the two machines behave differently: there the daemon is supervised, so it comes back after a reboot and TUIOS restores the sessions. On `.27` the daemon is not supervised — it only survives because it detached from that terminal.
**This needs no rebuild.** It needs one decision, at a moment when losing the sessions is acceptable:
```sh
tuios list-sessions # see what you would lose (11 today)
tuios kill-server # stop the hand-started daemon
systemctl --user restart tuios.service
systemctl --user status tuios.service # should be active, restarts 0
```
After that the unit owns the daemon, it starts at login, and the retry loop stops. Do it when you are not in the middle of something on `.27` — the `security` session I just created lives in that daemon and will go away with it.
The rebuilds you still owe are unrelated:
```sh
# .51 only, if you have not done it yet
sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad
```
`.27` is already on the new generation, since `office` resolved by name there.
I have changed nothing since the last commit except the code you already have (`55085ba`). Say which of these you want done and I will do only that.
## Evidence
- Confidence: 86%
- Reason: Preserve full sanitized discovery detail from a rich investigation turn
## Related
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
## Related learned memories <!-- same-turn-2026-11-12 06:28:29 -->
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|TUIOS on .27: hand-started daemon (PID 1603888, 11 sessions) vs retrying systemd unit]]
- runbook: [[packs/paperclip-harness/70-runbooks/tmp-deployment-operations|TUIOS .27: hand daemon → systemd-supervised daemon (no rebuild)]]
- decision: [[packs/paperclip-harness/50-decisions/paperclip-harness-ui-six-workspaces-spec-first-four-panel-li|Paperclip project folders should live on .13; non-git folders use local_path; commands self-read secrets …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-multi-machine-setup-program-delivery-git-w|Paperclip harness multi-machine setup: program delivery, git worktrees vs local_path, env var inheritance …[truncated]…]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-cannot-2026-11-12-06-28-28|Rich discovery: cannot 2026-11-12 06:28:28]]
- context: [[packs/paperclip-harness/20-context/home|cannot]]

View File

@@ -0,0 +1,98 @@
---
type: session
id: session.paperclip-harness.rich-discovery-config-2026-11-12-06-19-59
title: Rich discovery: .config 2026-11-12 06:19:59
status: active
source_of_truth: false
freshness: current
last_reviewed: 2026-11-12
tags:
- pack/paperclip-harness
- agent-memory/session
- autolearn
- rich-discovery
- repo/config
---
# Rich discovery: .config 2026-11-12 06:19:59
Sanitized rich discovery snapshot captured automatically from the completed turn.
## Source paths observed
- `paperclip-harness/tui/paperclip`
- `home/sam/SECURITY`
- `HOME/.local/share/paperclip-harness/bin/office`
- `home/sam/SECURITY/.harness/harness.toml`
- `nix/store/m15fsh578q4yplc2hi12gp5xsyn6zkq2-tuios-0.12.1/bin/tuios`
- `etc/profiles/per-user/sam/bin/tuios`
- `home/sam/.config/paperclip-harness/projects.toml`
- `home/sam/SECURITY/.harness/check.sh`
- `home/sam/SECURITY/.harness/NOTES.md`
- `.config/environment.d/10-secrets.conf`
- `.local/share/paperclip-harness/bin`
## Final answer snapshot
Fixed, and your `security` project is now running.
## What the error was
Two things, and the first hid the second.
**1. The key is not in your shell on `.27`.** `PAPERCLIP_BOARD_KEY` lives in `~/.config/environment.d/10-secrets.conf`, which systemd's user manager reads. On `.13` the session inherits it; on `.27` your terminal is started by the desktop session, which does not import `environment.d`. So the file was right there and the variable was empty.
**2. Even with the key, `office` on `.27` was aiming at the wrong machine.** The default address was `http://127.0.0.1:3100`, and Paperclip runs on `.13`. `.27` can reach it — I checked, 200 — but nothing was asking that address.
Both are fixed in the code, on the Python commands and in the Go program, so it works from a shell and from inside a pane:
- the key, the company id and the API address are read from `~/.config/environment.d/*.conf` when the environment does not have them (read only; never printed);
- if `PAPERCLIP_API_URL` is not set and `127.0.0.1:3100` does not answer, the address becomes `http://192.168.20.13:3100`.
## A third problem your command would have hit
`~/SECURITY` is **not a git repository**, and `--new` always created a project with a branch per task in a worktree. A worktree needs a repository, so every run on that project would have failed. `--new` now checks, and for a folder that is not a repository it creates a `local_path` project: the folder stays in place and a task changes the files directly. It says so when it does that.
## It is created and running
```
project security 8b33e222-8b62-40a2-950d-dbc9a3a2ffcd
source local_path cwd /home/sam/SECURITY
policy none (no worktrees: not a git repository)
session security on .27, 8 panes
panes board · work · agents · reply · new · project · repair = harness
shell = a shell
```
Attach with `tuios attach security` on `.27`.
If you would rather have a branch per task for that folder, `git init` it and re-run `office ~/SECURITY --project security`; the project keeps the folder but you would need a new one to get the worktree policy, because the policy is set when the project is created.
## Two things I noticed on `.27` while doing this
- **`tuios.service` is in `activating (auto-restart)`** — the unit keeps failing to start, while a manually started daemon (pid 1603888) is what your sessions actually live on. That is why your sessions exist at all. After a reboot the manual daemon will not be there, so this is worth fixing before you rely on `.27` surviving a restart.
- The `.27` rebuild for `~/.local/bin` on `PATH` clearly took effect, since `office` resolved by name. **`.51` still needs its rebuild** if you have not done it yet:
```sh
sudo nixos-rebuild switch --flake /etc/nixos#sam-thinkpad
```
## Committed
`paperclip-harness` → `55085ba`. The updated commands are also copied to `.27` and `.51` at `~/.local/share/paperclip-harness/bin/`, with `--help` working on both.
## Evidence
- Confidence: 86%
- Reason: Preserve full sanitized discovery detail from a rich investigation turn
## Related
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
## Related learned memories <!-- same-turn-2026-11-12 06:19:59 -->
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-config-2026-11-12-06-19-59|Rich discovery: .config 2026-11-12 06:19:59]]
- context: [[packs/paperclip-harness/20-context/config|.config]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|.config repository structure and configuration patterns]]

69
bin/lib/repo.py Normal file
View File

@@ -0,0 +1,69 @@
"""What a folder needs to be before Paperclip can give a task its own worktree.
A worktree is a git feature: Paperclip makes a directory and a branch per task
from the project's repository. A folder that is not a repository cannot have
one, and a repository with no commit has no branch to cut from. Both cases are
checked here, and neither is repaired without being asked.
"""
import os
import subprocess
def git(folder, *args, timeout=60):
return subprocess.run(["git", "-C", folder] + list(args),
capture_output=True, text=True, timeout=timeout)
def is_repo(folder):
if not os.path.isdir(folder):
return False
return git(folder, "rev-parse", "--is-inside-work-tree").stdout.strip() == "true"
def has_commit(folder):
return git(folder, "rev-parse", "--verify", "HEAD").returncode == 0
def branch(folder):
"""The branch to cut task branches from: what is checked out, else main, else master."""
out = git(folder, "symbolic-ref", "--short", "HEAD").stdout.strip()
if out:
return out
for name in ("main", "master"):
if git(folder, "rev-parse", "--verify", name).returncode == 0:
return name
return "main"
def untracked_and_modified(folder):
out = git(folder, "status", "--porcelain").stdout.splitlines()
return len([ln for ln in out if ln.strip()])
def has_ignored(folder):
return os.path.isfile(os.path.join(folder, ".gitignore"))
def make_repo(folder, quiet=False):
"""git init, add everything, and one commit. Returns (ok, message)."""
say = (lambda *_: None) if quiet else print
steps = [
("init", ["init", "-b", "main"]),
]
for label, args in steps:
r = git(folder, *args)
if r.returncode != 0:
return False, "git %s failed: %s" % (label, (r.stderr or r.stdout).strip()[:200])
if git(folder, "add", "-A").returncode != 0:
return False, "git add failed"
r = git(folder, "-c", "user.name=Paperclip Harness",
"-c", "user.email=harness@localhost",
"commit", "-q", "-m", "Initial commit, before any Paperclip task")
if r.returncode != 0:
detail = (r.stderr or r.stdout).strip()
if "nothing to commit" in detail:
return False, "the folder has no files to commit"
return False, "git commit failed: %s" % detail[:200]
say(" made a repository on branch %s, one commit" % branch(folder))
return True, ""

View File

@@ -27,11 +27,13 @@ import shlex
import socket import socket
import subprocess import subprocess
import sys import sys
import urllib.parse
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "lib")) sys.path.insert(0, os.path.join(HERE, "lib"))
import harness as h # noqa: E402 import harness as h # noqa: E402
import project as pj # noqa: E402 import project as pj # noqa: E402
import repo as rp # noqa: E402
import tuios # noqa: E402 import tuios # noqa: E402
@@ -43,7 +45,8 @@ def parse(argv):
up when someone uses the flag. up when someone uses the flag.
""" """
opts = {"folder": ".", "folder_given": False, "project": "", "session": "", opts = {"folder": ".", "folder_given": False, "project": "", "session": "",
"dry": False, "host": "", "no_switch": False, "new": "", "remote": ""} "dry": False, "host": "", "no_switch": False, "new": "", "remote": "",
"git_init": "--git-init" in argv, "anywhere": "--anywhere" in argv}
positional = [] positional = []
i = 0 i = 0
while i < len(argv): while i < len(argv):
@@ -77,8 +80,7 @@ def create_project(name, folder, dry):
# A folder that is a git repository gets a branch per task in its own # A folder that is a git repository gets a branch per task in its own
# worktree. A folder that is not cannot: asking for a worktree would create # worktree. A folder that is not cannot: asking for a worktree would create
# a project whose every run fails. Those keep the folder in place instead. # a project whose every run fails. Those keep the folder in place instead.
is_repo = subprocess.run(["git", "-C", folder, "rev-parse", "--is-inside-work-tree"], is_repo = rp.is_repo(folder) and rp.has_commit(folder)
capture_output=True, text=True, timeout=30).stdout.strip() == "true"
body = { body = {
"name": name, "name": name,
"description": "Adopted from %s by bin/office." % folder, "description": "Adopted from %s by bin/office." % folder,
@@ -95,7 +97,7 @@ def create_project(name, folder, dry):
"enabled": True, "enabled": True,
"defaultMode": "isolated_workspace", "defaultMode": "isolated_workspace",
"allowIssueOverride": True, "allowIssueOverride": True,
"workspaceStrategy": {"type": "git_worktree", "baseRef": "main", "workspaceStrategy": {"type": "git_worktree", "baseRef": rp.branch(folder),
"branchTemplate": "{{issue.identifier}}-{{slug}}"}, "branchTemplate": "{{issue.identifier}}-{{slug}}"},
} }
else: else:
@@ -267,6 +269,9 @@ def run_on_remote(host, folder, opts):
for flag, value in (("--project", opts["project"]), ("--session", opts["session"]), ("--new", opts["new"])): for flag, value in (("--project", opts["project"]), ("--session", opts["session"]), ("--new", opts["new"])):
if value: if value:
inner += [flag, value] inner += [flag, value]
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
if opts["dry"]: if opts["dry"]:
inner.append("--dry") inner.append("--dry")
# "$HOME" must stay unquoted so the other machine's shell expands it; every # "$HOME" must stay unquoted so the other machine's shell expands it; every
@@ -308,6 +313,8 @@ COMMANDS
office <folder> --session <name> name the session instead of the folder office <folder> --session <name> name the session instead of the folder
office <folder> --project "<Name>" --host <machine> the folder is on that machine office <folder> --project "<Name>" --host <machine> the folder is on that machine
office <folder> --new "<Name>" --dry say what would happen, write nothing office <folder> --new "<Name>" --dry say what would happen, write nothing
office <folder> --new "<Name>" --git-init make the folder a repository first
office <folder> --new "<Name>" --anywhere allow it although agents cannot see it
board create any missing pane in this project's session board create any missing pane in this project's session
board --folder <folder> the same, for another project board --folder <folder> the same, for another project
@@ -341,6 +348,23 @@ EVERY MACHINE, FROM THE TERMINAL
tuios attach <session> on this machine tuios attach <session> on this machine
tuios attach --host nixos-desktop <session> to a session on another machine tuios attach --host nixos-desktop <session> to a session on another machine
WORKTREES, AND WHY GIT MATTERS
A new project gets a worktree policy: Paperclip makes a branch and a separate
directory per task, so two tasks never change the same files, and there is a
diff to review and a merge to approve.
That needs a repository with at least one commit. When the folder is not one,
office says so and asks before touching anything: making a repository means
committing every file in the folder, which is a decision about your own files.
Without it the project is created in place (local_path): a task edits the
folder directly, there is no diff and nothing to merge.
The base branch is read from the folder, not assumed to be main.
WHERE THINGS RUN
Panes run on the machine that owns the folder. Agents run on the machine that
serves Paperclip, because Paperclip starts them itself. If those differ, the
panes work and the agents cannot see the files, so --new refuses unless
--anywhere is given.
ON ANOTHER MACHINE ON ANOTHER MACHINE
--host copies this folder of commands to the other machine, at --host copies this folder of commands to the other machine, at
~/.local/share/paperclip-harness/bin/ ~/.local/share/paperclip-harness/bin/
@@ -366,6 +390,94 @@ MORE
""" """
def paperclip_machine():
"""The machine that serves Paperclip, as a name we know, or the address."""
addr = urllib.parse.urlparse(h.API).hostname or ""
if addr in ("127.0.0.1", "localhost", "::1"):
return pj.this_machine()
for name, known in HOSTS.items():
if known == addr:
return name
return addr
def guard_machine(folder, opts):
"""Say plainly when the panes and the agents would be on different machines.
Panes run where the folder is. Agents run where Paperclip is, because
Paperclip starts them itself. A folder on the laptop therefore gives panes
that work and agents that cannot find the files. That is worth stopping for
when a project is being created; an existing project may still be worth
opening just to read it.
"""
if not opts["new"] and not opts["anywhere"]:
# Adopting: a note is enough, and only when it is true.
pass
mine = HOSTS.get(pj.this_machine(), pj.this_machine())
theirs = HOSTS.get(paperclip_machine(), paperclip_machine())
if mine == theirs:
return
text = [
"this folder is on %s, but Paperclip and its agents run on %s" % (pj.this_machine(), paperclip_machine()),
"the panes will open here and will read this folder correctly",
"the agents will not: they start on %s, where %s does not exist" % (paperclip_machine(), folder),
"a task you assign will fail, or work on a different folder",
"move the folder to %s, mount it there, or pass --anywhere to go ahead anyway" % paperclip_machine(),
]
if opts["new"] and not opts["anywhere"]:
raise h.Refused(2, "\n".join(text))
for i, line in enumerate(text):
h.say(("warning: " if i == 0 else " ") + line)
def prepare_git(folder, opts):
"""Make the folder able to hold a worktree per task, and say what it did.
A worktree needs a repository and at least one commit. Neither is created
silently: a first commit is a decision about a person's own files, and for a
folder of notes it may include things that should not be in a history at
all. Returns True when the folder is ready for worktrees.
"""
if rp.is_repo(folder) and rp.has_commit(folder):
return True
if rp.is_repo(folder) and not rp.has_commit(folder):
what = "the repository at %s has no commit yet" % folder
else:
what = "%s is not a git repository" % folder
if opts["dry"]:
return False
h.say("%s, so each task cannot have its own branch and worktree." % what)
h.say(" without them, two tasks can change the same files at once, and there")
h.say(" is no diff to review and nothing to merge.")
if opts["git_init"]:
go = True
elif sys.stdin.isatty():
h.say("")
h.say("make it a repository now? This runs: git init -b main, git add -A,")
h.say("and one commit of EVERY file in the folder, including anything you")
h.say("have not noticed. Add a .gitignore first if that is not what you want.")
answer = input(" type yes to do it: ").strip().lower()
go = answer in ("y", "yes")
else:
go = False
h.say("")
h.say("not a terminal, so nothing was changed. Either run this again and")
h.say("answer the question, or do it yourself:")
h.say(" cd %s" % folder)
if rp.is_repo(folder):
h.say(" git add -A && git commit -m 'Initial commit'")
else:
h.say(" git init -b main && git add -A && git commit -m 'Initial commit'")
h.say("or pass --git-init to let this command do it.")
if not go:
return False
ok, why = rp.make_repo(folder)
if not ok:
h.say(" could not: %s" % why)
return False
return True
def main(): def main():
argv = sys.argv[1:] argv = sys.argv[1:]
if not argv or "-h" in argv or "--help" in argv: if not argv or "-h" in argv or "--help" in argv:
@@ -412,11 +524,18 @@ def main():
else: else:
return run_on_remote(opts["remote"], folder, opts) return run_on_remote(opts["remote"], folder, opts)
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
if opts["dry"]: if opts["dry"]:
if opts["new"]: if opts["new"]:
h.say("would create a Paperclip project %r for %s" % (opts["new"], folder)) h.say("would create a Paperclip project %r for %s" % (opts["new"], folder))
else: else:
h.say("would adopt the Paperclip project %r for %s" % (opts["project"] or "<from .harness/harness.toml>", folder)) h.say("would adopt the Paperclip project %r for %s" % (opts["project"] or "<from .harness/harness.toml>", folder))
if ready:
h.say("worktrees: yes — a repository on branch %r, so each task gets its own" % rp.branch(folder))
else:
h.say("worktrees: no — %s" % ("this is not a repository" if not rp.is_repo(folder) else "the repository has no commit"))
h.say("would write %s" % os.path.join(folder, ".harness", "harness.toml")) h.say("would write %s" % os.path.join(folder, ".harness", "harness.toml"))
h.say("would create or reuse session %r and build eight panes there" % session) h.say("would create or reuse session %r and build eight panes there" % session)
h.say("dry run: nothing was written") h.say("dry run: nothing was written")
@@ -457,6 +576,9 @@ def main():
"watching": watching, "watching": watching,
} }
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
if opts["dry"]: if opts["dry"]:
h.say("would write %s" % pj.path(folder)) h.say("would write %s" % pj.path(folder))
h.say("would build pages: shell, tree, reply, log, new") h.say("would build pages: shell, tree, reply, log, new")

View File

@@ -67,6 +67,56 @@ leave pages missing.
Inside a pane, `R` in `Repair>repair` rebuilds everything; `B` only adds what is missing. Inside a pane, `R` in `Repair>repair` rebuilds everything; `B` only adds what is missing.
## Worktrees, and the folder that is not a repository
A new project gets a worktree policy: Paperclip makes a branch and a separate directory per task,
so two tasks never change the same files, and there is a diff to review and a merge to approve.
That needs a repository **with at least one commit**. `office` checks, and when the folder is not
one it explains what is lost and asks before touching anything:
```
/tmp/officetest is not a git repository, so each task cannot have its own branch and worktree.
without them, two tasks can change the same files at once, and there
is no diff to review and nothing to merge.
make it a repository now? This runs: git init -b main, git add -A,
and one commit of EVERY file in the folder, including anything you
have not noticed. Add a .gitignore first if that is not what you want.
type yes to do it:
```
Three answers, in order of preference:
1. **Answer the question.** It only appears at a terminal, so nothing is created by a script by
accident.
2. **`--git-init`** does the same without asking, for a script.
3. **Do it yourself**, which is what a machine without a terminal is told to do, with the exact
commands.
If you say no, or there is no terminal and no flag, the project is still created — as
`local_path`, in place. A task then edits the folder directly: no branch, no diff, nothing to
merge. That is honest for a folder of notes and wrong for code.
The base branch is read from the folder (`git symbolic-ref`), not assumed to be `main`, so a
repository on `master` works.
## A folder on a machine that does not serve Paperclip
Panes run where the folder is. **Agents run where Paperclip is**, because Paperclip starts them
itself. When those are different machines the panes read the folder correctly and the agents
cannot see it at all. `office --new` therefore refuses, and says why:
```
refused: this folder is on sam-4screen-desktop, but Paperclip and its agents run on nixos-desktop
the panes will open here and will read this folder correctly
the agents will not: they start on nixos-desktop, where /home/sam/SECURITY does not exist
a task you assign will fail, or work on a different folder
move the folder to nixos-desktop, mount it there, or pass --anywhere to go ahead anyway
```
`--anywhere` goes ahead anyway, for the case where the panes are all you want.
## Traps this design keeps from the previous one ## Traps this design keeps from the previous one
- A pane's command must carry the project, or a rebuild shows the wrong one. - A pane's command must carry the project, or a rebuild shows the wrong one.