agent-peer 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. agent_peer-0.2.1/.dev/CHARTER.md +66 -0
  2. agent_peer-0.2.1/.dev/HANDOFF.md +464 -0
  3. agent_peer-0.2.1/.dev/log.md +5 -0
  4. agent_peer-0.2.1/.dev/reviews/agy-review.md +74 -0
  5. agent_peer-0.2.1/.dev/reviews/claude-review.md +111 -0
  6. agent_peer-0.2.1/.dev/reviews/opencode-report.md +124 -0
  7. agent_peer-0.2.1/.dev/reviews/opencode-review.md +226 -0
  8. agent_peer-0.2.1/.dev/reviews/pi-review.md +106 -0
  9. agent_peer-0.2.1/.github/workflows/release.yml +93 -0
  10. agent_peer-0.2.1/.gitignore +25 -0
  11. agent_peer-0.2.1/AGENTS.md +95 -0
  12. agent_peer-0.2.1/CHANGELOG.md +56 -0
  13. agent_peer-0.2.1/CLAUDE.md +1 -0
  14. agent_peer-0.2.1/LICENSE +21 -0
  15. agent_peer-0.2.1/PKG-INFO +170 -0
  16. agent_peer-0.2.1/README.md +142 -0
  17. agent_peer-0.2.1/agent_peer/__init__.py +3 -0
  18. agent_peer-0.2.1/agent_peer/__main__.py +4 -0
  19. agent_peer-0.2.1/agent_peer/agy_live.py +102 -0
  20. agent_peer-0.2.1/agent_peer/agy_status.py +148 -0
  21. agent_peer-0.2.1/agent_peer/claude_status.py +195 -0
  22. agent_peer-0.2.1/agent_peer/cli.py +273 -0
  23. agent_peer-0.2.1/agent_peer/inbox.py +124 -0
  24. agent_peer-0.2.1/agent_peer/listener.py +306 -0
  25. agent_peer-0.2.1/agent_peer/logs.py +354 -0
  26. agent_peer-0.2.1/agent_peer/protocol.py +130 -0
  27. agent_peer-0.2.1/agent_peer/registry.py +132 -0
  28. agent_peer-0.2.1/agent_peer/sender.py +63 -0
  29. agent_peer-0.2.1/assets/agent-peer-architecture.svg +97 -0
  30. agent_peer-0.2.1/docs/agent-peer-weaknesses-report.md +66 -0
  31. agent_peer-0.2.1/docs/notification-click-target.md +42 -0
  32. agent_peer-0.2.1/docs/stale-listener-detection.md +72 -0
  33. agent_peer-0.2.1/docs/status.md +123 -0
  34. agent_peer-0.2.1/docs/tasks/0001-inbox-files-are-not-world-readable.md +27 -0
  35. agent_peer-0.2.1/docs/tasks/0002-cursor-never-silently-eats-a-backlog.md +33 -0
  36. agent_peer-0.2.1/docs/tasks/0003-session-list-shows-idle-after-messages-are-read.md +29 -0
  37. agent_peer-0.2.1/docs/wait-unread-cursor.md +101 -0
  38. agent_peer-0.2.1/pyproject.toml +55 -0
  39. agent_peer-0.2.1/skills/README.md +20 -0
  40. agent_peer-0.2.1/skills/agy/README.md +31 -0
  41. agent_peer-0.2.1/skills/agy/SKILL.md +66 -0
  42. agent_peer-0.2.1/skills/opencode/README.md +32 -0
  43. agent_peer-0.2.1/skills/opencode/SKILL.md +63 -0
  44. agent_peer-0.2.1/skills/pi/README.md +33 -0
  45. agent_peer-0.2.1/skills/pi/SKILL.md +72 -0
  46. agent_peer-0.2.1/tests/__init__.py +0 -0
  47. agent_peer-0.2.1/tests/helpers.py +72 -0
  48. agent_peer-0.2.1/tests/test_auth.py +103 -0
  49. agent_peer-0.2.1/tests/test_cli_smoke.py +36 -0
  50. agent_peer-0.2.1/tests/test_cursor.py +125 -0
  51. agent_peer-0.2.1/tests/test_permissions.py +71 -0
@@ -0,0 +1,66 @@
1
+ # Charter
2
+
3
+ Written by the owner. Read by every session at start. Changes rarely.
4
+
5
+ ## What we are building, and for whom
6
+
7
+ `agent-peer` is a local IPC mesh so any agent harness (Claude Code, Antigravity/agy,
8
+ pi, opencode, others later) can discover, message, and reactively wake up any other
9
+ harness session on the same machine — no polling, no per-harness configuration. For
10
+ the owner (rg): use agent-peer daily, across harnesses, without worrying about
11
+ hidden bugs that only surface once it's actually relied on.
12
+
13
+ ## Current milestone
14
+
15
+ **M1: Use agent-peer without worrying about hidden bugs.**
16
+
17
+ Close out the highest-priority findings from the 4 review documents
18
+ (`.dev/reviews/*.md`) written 17 Sep 2026 by agy, pi, opencode, and Claude —
19
+ especially the ones independently confirmed by 2+ reviewers, and the ones about
20
+ security/data correctness (not just documentation convenience).
21
+
22
+ **Gate:** Owner runs `agent-peer list` and `agent-peer wait` once more after every
23
+ M1 task is closed, and nothing looks off.
24
+
25
+ ## Who owns what
26
+
27
+ Paths, never responsibilities. A path with no owner is nobody's, and changing it is a
28
+ request to the owner rather than an edit.
29
+
30
+ | Path | Owner |
31
+ | --- | --- |
32
+ | `agent_peer/*.py` (core package) | `agent-peer-e4` |
33
+ | `~/.gemini/antigravity/skills/agent-peer/SKILL.md`, `~/.gemini/GEMINI.md` (bagian agent-peer) | `antigravity-test` |
34
+ | `~/.pi/agent/skills/agent-peer/SKILL.md`, `~/.pi/agent/AGENTS.md` (bagian agent-peer) | `pi-98661` |
35
+ | `~/.config/opencode/skills/agent-peer/SKILL.md`, `~/.config/opencode/AGENTS.md` (bagian agent-peer) | `opencode-15297` |
36
+ | `.dev/reviews/<name>-review.md` | the session with the matching name, each owns their own |
37
+ | `docs/`, `README.md`, `.dev/CHARTER.md`, `pyproject.toml` | owner only (rg) |
38
+
39
+ **Not owned by anyone working here:** git operations (commit/push/branch) — owner
40
+ only, per rg's global instructions. Killing/terminating other agents' processes —
41
+ owner only.
42
+
43
+ ## Decisions that are already made
44
+
45
+ - **Test policy:** all verification during the 17 Sep 2026 session was done live and
46
+ manually (ad-hoc scripts + real cross-harness tests), there's no automated test
47
+ suite in the repo yet — that's itself one of the M1 findings (see reviews), but
48
+ there's no mandatory-test rule for other tasks until the base suite exists.
49
+ - **Don't auto-spawn a new process as a side effect of another command.** Tried once
50
+ (`wait` auto-starting `listen`), the owner rejected it — risk of listeners silently
51
+ piling up. If a new process is needed, it must be explicit from the user/agent, not
52
+ automatic.
53
+ - **`wait` doesn't hard-refuse when unreachable** — just a warning (stderr), still
54
+ proceeds (reading an already-piled-up backlog is a valid use case even without a
55
+ live listener).
56
+ - **Auto session-name (`detect_harness_identity`) deliberately doesn't guess a
57
+ per-tool env var** — walking the parent-process chain was chosen because it's more
58
+ reliable and needs no cooperation from each harness. Its limitation (subagents
59
+ sharing a parent collide) is known — that's an M1 task.
60
+ - **`--timeout` is not a default that gets forced onto anyone** — briefly
61
+ recommended then rejected by the owner for the mandatory-standby pattern
62
+ (background task); leave it per-context, don't hardcode a specific timeout
63
+ recommendation into any skill.
64
+ - Editable install (`uv tool install --editable .`) — source changes are
65
+ automatically live on the installed binary with no manual reinstall. A process
66
+ already running before an edit keeps using the old code until it's restarted.
@@ -0,0 +1,464 @@
1
+ # HANDOFF
2
+
3
+ ## 2026-09-17 — `wait`/`watch` mechanism analysis, unread cursor plan
4
+
5
+ **Context:** user actively uses `agent-peer` for Claude Code <-> Antigravity
6
+ (agy) communication. This discussion session was pure research + planning,
7
+ no code implementation yet.
8
+
9
+ **`wait` vs `watch` mechanism findings:**
10
+ - `wait` (`inbox.py:wait_for_message`) = blocking tool call, the only thing
11
+ that genuinely triggers agy's "auto wakeup" (process exit = control back
12
+ to the LLM loop). But fragile: this process dies every time agy moves to
13
+ another tool call/task, so it has to be manually restarted every time work
14
+ finishes.
15
+ - `watch` (`logs.py:show_logs` follow mode) = a background process that
16
+ never exits on its own, stays alive across tasks — good for passive
17
+ visibility, but doesn't prove it triggers wakeup on its own (depends on
18
+ whether the agy harness monitors its stdout as an async notification —
19
+ unconfirmed).
20
+ - `notify_idle` in `peerFeatures` (listener.py:103) checked: cosmetic field,
21
+ no implementation anywhere in this codebase reads it.
22
+
23
+ **Concrete bugs found in `wait_for_message`:**
24
+ 1. Baseline computed from `len(inbox)` at the moment `wait` is called →
25
+ backlog messages that arrive while `wait` is dead (agy busy working)
26
+ never get automatically "claimed" when `wait` restarts.
27
+ 2. `return msgs[-1]` only grabs the last message → if several messages pile
28
+ up within one poll cycle (100ms), the rest are permanently lost (the next
29
+ baseline already considers them "old").
30
+
31
+ **Decision & status:** design in
32
+ [`docs/wait-unread-cursor.md`](../docs/wait-unread-cursor.md) — per-session
33
+ timestamp-based cursor (`~/.agent-peer/cursors/<name>.json`), auto-advances
34
+ when `wait` is called (no explicit "mark as read" needed from the agent),
35
+ `wait` returns a list instead of 1 message. **Implemented**
36
+ (`protocol.py`, `inbox.py`, `cli.py`) and verified via isolated tests (`HOME`
37
+ override, plus a simulation using a read-only copy of the real production
38
+ antigravity inbox with 42 messages — first-call-after-restart proven not to
39
+ replay old history). Local install is editable, so source changes are
40
+ automatically live for the next `agent-peer` invocation; `listen`/`wait`
41
+ processes already running before an edit only pick up the new code after
42
+ being restarted. A settle-window/delay before returning was considered then
43
+ decided against (user's usage pattern isn't sub-second bursts, the existing
44
+ backlog-merge is already enough).
45
+
46
+ **Separate finding (operational, not a code bug):** `agent-peer list` shows
47
+ `antigravity-2` (PID 71277) as ALIVE=yes even though the user thought they'd
48
+ closed that session. Verified not PID-reuse (procStart matches `ps lstart`)
49
+ — the `agent-peer listen` process is genuinely still alive, it just never
50
+ received SIGTERM/SIGINT when the UI session was closed, so `cleanup()` in
51
+ `listener.py` never got called. **Correction:** initially suspected this
52
+ could be detected via detached TTY (`??`) — turned out wrong, the actively-
53
+ used `antigravity` process (72769) has an identical TTY/PPID/STAT (`??`,
54
+ ppid=1/launchd, `S`). There's no OS-level signal that distinguishes stale
55
+ from active. Fix plan (based on duplicate-name-group detection + recency,
56
+ plus a `stop` command using SIGTERM) is in
57
+ [`docs/stale-listener-detection.md`](../docs/stale-listener-detection.md).
58
+ Temporary manual fix: `kill <pid>`.
59
+
60
+ **Dead end:** briefly suspected there was an active push/interrupt mechanism
61
+ from `agent-peer` to the agy process for wakeup — investigated via a fork
62
+ agent, found nothing. No signal/callback of any kind exists; everything
63
+ relies on the "blocking tool call" pattern that agy itself initiates, not
64
+ any wiring on the `agent-peer` side.
65
+
66
+ **Live end-to-end verification (real agy session, `antigravity-test` PID
67
+ 52285):** sent 2 messages (5 second gap) while agy was busy with another
68
+ task. Proven by comparing cursor timestamp vs message `received_at` (exact
69
+ match in both cases): message 1 triggered an instant wakeup while `wait` was
70
+ blocking; message 2 piled up while busy then got auto-consumed when `wait`
71
+ was called again after the task finished. The cursor/backlog mechanism
72
+ proven to work under real conditions, not just isolated unit tests.
73
+
74
+ **Follow-up found directly from the live session (`antigravity-test`):**
75
+ `ps` confirmed 2 `agent-peer wait --name antigravity-test` processes alive
76
+ at the same time (same PPID, different TTY) — the agy harness spawns a new
77
+ `wait` without closing the old one. Real race: both processes could
78
+ potentially catch the same message (duplicate, not lost). **Fix
79
+ implemented:** `cmd_wait` takes a non-blocking exclusive per-session lock
80
+ (`fcntl.flock` on `~/.agent-peer/locks/<session>.lock`) — a second
81
+ invocation for the same session fails immediately (`exit 1` + a clear
82
+ message) instead of silently racing. The lock is released automatically by
83
+ the OS when the process dies/crashes. Verified in sandbox: the second
84
+ process is rejected instantly, the first process is undisturbed, the lock
85
+ releases after the first process dies (including via `kill`), sessions with
86
+ different names don't block each other. Details in
87
+ [`docs/wait-unread-cursor.md`](../docs/wait-unread-cursor.md).
88
+
89
+ **Additional fixes done this session (outside the original plan, found/
90
+ requested as the session went on):**
91
+ - **Auth-bypass in `listener.py`** — found while dissecting agy's report
92
+ (worse than their finding 1.2): the `authenticated` variable was computed
93
+ but never checked before `process_incoming_frame` was called, so the auth
94
+ token was never enforced at all. Fix: add `if not authenticated: continue`
95
+ before `user`/`control` frames get processed. Tested (auth-gate test):
96
+ frame without auth rejected, wrong token rejected, correct token accepted
97
+ — all PASS.
98
+ - **Notification click opens Script Editor instead of the relevant app** —
99
+ root cause is macOS behavior (notifications via `osascript` always get
100
+ attributed to Script Editor). Fix: switch to
101
+ `terminal-notifier -activate com.googlecode.iterm2` (falls back to
102
+ `osascript` if `terminal-notifier` isn't available). User confirmed
103
+ clicking the notification now opens iTerm. Details in
104
+ [`docs/notification-click-target.md`](../docs/notification-click-target.md).
105
+
106
+ **Multi-harness generalization (`agy`/`pi`/`opencode`/etc.) — auto-detected
107
+ session names:** the user wants `agent-peer` to stop being Antigravity-only.
108
+ Briefly tried detecting harness identity via env vars (grepping strings in
109
+ the `agy`/`pi`/`opencode` binaries) — inconclusive, lots of noise, no clear
110
+ identity env var. **Approach that proved to work:** walk up the parent
111
+ process chain (`os.getppid()` → `ps -o comm=`), skip generic shell/
112
+ interpreter names (`zsh`, `bash`, `python3`, etc.), use the first
113
+ distinctive process name found + that process's PID (not `agent-peer`'s own
114
+ PID, since `wait` is called repeatedly with a different PID each time — a
115
+ stable identity is needed, and the harness process itself is stable for the
116
+ life of the session). Validated directly against real data from this
117
+ session: `zsh` (PID 79203) → `claude` (PID 23386), `agy` (PID 33402) was
118
+ also already confirmed from earlier data. Implemented in
119
+ `protocol.py:detect_harness_identity`/`auto_session_name`, used lazily
120
+ (only computed when `--name`/`--sender` and `$AGENT_PEER_NAME` are both
121
+ empty) in `cmd_send`/`cmd_listen`/`cmd_wait` — other commands (`list`,
122
+ `status`, etc.) incur zero overhead from this (tested, `list` stays
123
+ ~0.05s). Agreed scope: every command (`wait`, `listen`, `send`), and `wait`
124
+ with no name now reads the per-harness inbox (auto-named) instead of the
125
+ merged global inbox — a deliberate decision, not a regression.
126
+
127
+ **Additional bug found & fixed while testing auto-name:** the
128
+ listen→send→wait-for-the-first-time sequence (before that session had ever
129
+ called `wait`) caused an already-arrived message to be **missed** — because
130
+ the cursor got initialized to "now" at the moment `wait` was FIRST called
131
+ (not when the session actually started), so a message that arrived before
132
+ that first `wait` call was treated as "already old". Fix:
133
+ `inbox.py:mark_session_start()` is called at the end of
134
+ `PeerListener.setup()` — initializes the cursor at the earliest possible
135
+ point (before `accept()` could ever process anything), instead of waiting
136
+ for the first `wait` call. Sessions already running before this fix remain
137
+ safe (still use the old fallback in `_read_cursor`, unchanged — the
138
+ `test_wait_cursor.py` regression suite still PASSES entirely).
139
+
140
+ `README.md` updated: the listen section generalized (no longer
141
+ Antigravity-only), a new section added about auto-detected names and an
142
+ explanation of `wait` (backlog/cursor/lock) that previously wasn't
143
+ documented in the README at all.
144
+
145
+ **Another bug found by the user:** `listener.py` hardcoded
146
+ `self.cwd = cwd or os.path.expanduser("~/projects")` — so the CWD column in
147
+ `agent-peer list` always showed `~/projects` regardless of the actual
148
+ directory `agent-peer listen` was run from. Fix: switched to `os.getcwd()`.
149
+ This is in core code (shared by every harness), so it automatically applies
150
+ to agy/pi/opencode at once, no per-harness work needed. Tested in sandbox
151
+ (cwd now correctly points to the real working directory). Live sessions
152
+ that were already registered wrong (`antigravity-test` PID 52285, `pi-98661`
153
+ PID 512) were manually patched to `/Users/rg/projects/agent-peer` to match
154
+ their actual current working context.
155
+
156
+ **Bug found by the user during a live `pi` test:** `listener.py` hardcoded
157
+ `"agentType": "AGY"` in the session json — a leftover from when this project
158
+ was still Antigravity-only. The `pi` session (`pi-98661`, PID 512) was
159
+ registered with the ENGINE column wrongly showing "AGY". Fix:
160
+ `PeerListener.__init__` accepts an optional `agent_type` (default fallback
161
+ `"AGENT"` if unknown), `cli.py:cmd_listen` detects the engine via
162
+ `detect_harness_identity()` (independent of the session name — even if
163
+ `--name` is given manually, the engine is still detected from the process
164
+ tree) and passes it to `PeerListener`. Tested in sandbox: new sessions now
165
+ correctly show `CLAUDE`/`PI`/etc. matching their parent process. The
166
+ `pi-98661` session json that was already wrong (started before the fix) was
167
+ manually patched (`agentType: "PI"`) — the listener process itself didn't
168
+ need restarting since the file is only read passively by `agent-peer list`,
169
+ not held under a continuous lock by the listener.
170
+
171
+ **Root cause of opencode only running `wait` without `listen`:** asked
172
+ `pi-98661` to audit this — the answer was sharp. Confirmed: `pi` itself
173
+ deliberately runs `listen` first (per its SKILL.md), BUT it identified a
174
+ real gap in the `SKILL.md`: there was never an explicit sentence saying
175
+ "wait alone isn't enough to be reachable — without a live listen, `send` to
176
+ your name will fail, and `wait` itself won't error even if you're
177
+ unreachable" — a silent failure mode that's easy to miss because `wait`
178
+ still "succeeds" (blocks normally) even though that session actually can't
179
+ be reached from outside.
180
+
181
+ **Briefly tried a radical fix (auto-spawn `listen` from inside `wait`), then
182
+ the user rejected it** — reason: a subprocess spawned automatically and
183
+ fully detached (`start_new_session=True`) risks silently piling up listeners
184
+ unnoticed, EXACTLY the "stale listener" problem that was already hard-won
185
+ diagnosed in [[stale-listener-detection]]. **Replaced with a non-invasive
186
+ guard:** `cmd_wait` now checks `resolve_session(session)` first — if it
187
+ fails, print a clear warning to stderr ("no listener running... run
188
+ agent-peer listen if you want to be reachable") then still proceed with the
189
+ wait as usual (not a hard refusal, since reading an already-piled-up backlog
190
+ without a live listener is still a valid use case). No new process is ever
191
+ created — it just informs, the decision stays with the agent/user. Tested:
192
+ warning appears exactly when unreachable, silent when already reachable, no
193
+ new listener appears in either case.
194
+
195
+ **Final decision on documentation:** considered then decided NOT to
196
+ propagate this further into SKILL.md/AGENTS.md — the runtime warning in
197
+ `wait` already closes the most dangerous gap (pi's point 2) more reliably
198
+ than static documentation (self-documenting at exactly the right moment,
199
+ doesn't depend on the agent reading the skill carefully).
200
+
201
+ **Four-way review collaboration (agy, pi, opencode, Claude) + handwalk task
202
+ system:** initialized `.dev/CHARTER.md` (M1 goal: "use agent-peer without
203
+ worrying about hidden bugs", gate: run `agent-peer list`+`wait` once more
204
+ and nothing looks off), a per-path ownership map across the 4 sessions. Out
205
+ of ~20 findings in `.dev/reviews/*.md`, triaged into 10 candidate tasks; the
206
+ 3 highest-priority ones (tasks 0001-0003) were done directly (not via
207
+ worktree, per the user's instruction — worktree deferred):
208
+
209
+ - **0001** chmod 0600 on inbox/cursor/lock files + 0700 on
210
+ `~/.agent-peer/*` directories (not `SOCKET_DIR`/`SESSIONS_DIR`, which
211
+ belong to Claude Code — deliberately left untouched).
212
+ - **0002** cursor safety: (a) `_read_cursor` on corruption now falls back
213
+ to `0` (replay everything) instead of `time.time()` (silently swallowing)
214
+ + a stderr warning; (b) `clear_inbox()` resets the cursor. **A regression
215
+ was found while testing this myself**: the initial fix DELETED the cursor
216
+ file on clear — that reopened the exact gap `mark_session_start` had
217
+ already closed (a message arriving between clear and the next wait would
218
+ get swallowed again). Fixed: the cursor is now **rewritten to the
219
+ moment-of-clear**, not deleted — exactly the same pattern as
220
+ `mark_session_start`.
221
+ - **0003** the `new-msg` status in the session json now gets reset to
222
+ `idle` (via `_reset_status_idle` in `cmd_wait`, best-effort through
223
+ `resolve_session`) every time `wait` successfully gets a message — a bug
224
+ `pi` found (status stuck forever after the first message, no code ever
225
+ wrote `idle` back).
226
+
227
+ All tested in sandbox (`test_wait_cursor.py`/`test_auth_gate.py` regressions
228
+ still PASS + new scenarios), **not yet hand-walked by the user** (handwalk
229
+ skill rule: a task can only be closed once the owner themself runs its
230
+ acceptance sentence on a real machine and writes down what happened). Task
231
+ files: `docs/tasks/0001-*.md` through `0003-*.md`. The remaining 7 candidate
232
+ tasks (sender-label resolution, "Delivered" wording, `_GENERIC_PROC_NAMES`
233
+ expansion, stdout warning, subagent `--name` documentation, test suite,
234
+ skills-into-repo) haven't been worked on yet — listed in chat, not yet
235
+ turned into formal task files.
236
+
237
+ **Live `opencode` validation complete, self-diagnosed by that session
238
+ itself** (full report in `.dev/reviews/opencode-report.md`, cross-checked
239
+ against the real cursor/inbox/lock files — all accurate). Root cause
240
+ exactly as predicted: opencode's bash tool is synchronous, default TOOL
241
+ timeout of 5 seconds (stricter than the 2 minutes guessed from initial web
242
+ research), plus the first session hadn't run `listen` first. Once the order
243
+ was right (`listen` detached → other work → `wait` as the last tool call
244
+ with no timeout), every feature (auto-name `opencode-15297`, ENGINE
245
+ `OPENCODE`, cwd-fix, backlog-merge, lock) validated identically to agy/pi.
246
+ **All three target harnesses (agy, pi, opencode) are now validated
247
+ end-to-end**, each with its own independent diagnosis from that agent
248
+ itself, not just a claim from this session.
249
+
250
+ **Important architecture correction from a live `pi` test:** the user asked
251
+ about the pattern `agent-peer wait; echo "WAIT-EXIT:$?" (timeout 600s)`
252
+ that showed up in a `pi-98661` tool call. Asked that session directly, the
253
+ answer was detailed and accurate (checked against `dist/core/tools/bash.js`
254
+ source by the pi agent itself): **`pi` has no background-bash at all**
255
+ (unlike Antigravity, which has `run_command` + a wakeup notification) — its
256
+ bash tool is fully synchronous. The 600s wasn't a pi infra limit, it was a
257
+ `timeout` parameter the agent itself passed to the tool call (SIGKILLs the
258
+ process tree if exceeded). Without a `timeout` on the tool call = genuinely
259
+ indefinite blocking.
260
+
261
+ Implication: the `SKILL.md` I originally wrote for `pi` had the wrong
262
+ "background task" framing (copied from the Antigravity version). **Already
263
+ fixed:**
264
+ - `wait` → stays a synchronous tool call, made the LAST tool call of the
265
+ turn (not "background"), with no `--timeout` on agent-peer NOR the pi
266
+ bash tool's own `timeout` (two different things that got mixed up) — the
267
+ turn stays "open" until a message arrives.
268
+ - `listen` → the opposite — it actually MUST be detached manually at the
269
+ shell level (`&`), because calling it synchronously would freeze the turn
270
+ forever (the `listen` process is designed to never exit on its own).
271
+ `AGENTS.md`/`README.md` weren't affected by this issue since they already
272
+ used the phrase "blocking shell call" from the start, never explicitly
273
+ said "background task" like `SKILL.md` did.
274
+
275
+ **`agent-peer` skill for `opencode`:** researched its convention, turned out
276
+ different again from agy/pi — confirmed via WebFetch to
277
+ `opencode.ai/docs/skills/`: frontmatter requires `name`+`description`
278
+ (optional `license`/`compatibility`/`metadata`), **no auto-load or slash
279
+ command** — the skill is invoked explicitly via the tool call
280
+ `skill({ name: "agent-peer" })`. Global location:
281
+ `~/.config/opencode/skills/<name>/SKILL.md` (there's also a fallback to
282
+ `~/.claude/skills/`/`~/.agents/skills/`, deliberately not used so it doesn't
283
+ bleed into the real Claude Code install). Created at
284
+ `~/.config/opencode/skills/agent-peer/SKILL.md`.
285
+
286
+ On opencode's bash tool: web research found a default of **synchronous,
287
+ 2-minute timeout** (env var `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS`
288
+ to change it), plus a `run_in_background` feature (similar to the
289
+ Antigravity pattern, auto re-invoke on command exit) — but **not confirmed
290
+ whether this feature exists in the installed opencode version (1.18.28)**,
291
+ the source was a PR that might be recent. Learning from the earlier wrong
292
+ assumption about `pi`, this SKILL.md was DELIBERATELY written without
293
+ assuming either way (gives 2 paths: background-capable vs
294
+ synchronous-only, asks the agent to check first) — **not yet validated
295
+ live**, waiting for the user to open a real `opencode` session to test it
296
+ exactly like `agy`/`pi`.
297
+
298
+ **`agent-peer` skill for `pi`:** injected at
299
+ `~/.pi/agent/skills/agent-peer/SKILL.md` (the native location read directly
300
+ by pi's core, confirmed from the `skill-store.ts` source — sits alongside
301
+ the bundled `web-search` skill). Same frontmatter format
302
+ (`name`+`description`), content adapted from the Antigravity SKILL.md but
303
+ more concise, matching the `web-search` style. **Not yet validated live**
304
+ whether `/agent-peer` in `pi` actually resolves to this file — `pi -p`
305
+ failed due to an auth error (`UnrecognizedClientException`) unrelated to
306
+ our change. User is testing an interactive `pi` session manually to
307
+ confirm.
308
+
309
+ **Other harnesses' global AGENTS.md:** checked 6 files (`pi`, `opencode`,
310
+ Codex, Hermes, Gemini x2) — all empty regarding `agent-peer` (turns out the
311
+ instruction for `agy` to use `agent-peer wait --name antigravity` has always
312
+ been purely manual, typed by the user every session, not from any permanent
313
+ config in `~/.gemini/AGENTS.md`). With the user's approval, a concise
314
+ "Cross-agent messaging (agent-peer)" section was added to
315
+ `~/.pi/agent/AGENTS.md` and `~/.config/opencode/AGENTS.md` (identical
316
+ content, just different auto-detect name examples, `pi-<pid>` vs
317
+ `opencode-<pid>`) — covering: `listen`/`wait` without `--name`, `send`,
318
+ `list`, pointing to the README for details (no content duplication).
319
+ `~/.gemini/GEMINI.md` (agy's actual global config — not
320
+ `~/.gemini/AGENTS.md`/`~/.gemini/config/AGENTS.md`, which turned out unused,
321
+ containing the "SuperAntigravity Skills" framework instead) also got the
322
+ same section added (example name adjusted to `agy-<pid>`). Codex/Hermes
323
+ AGENTS.md **not** touched yet — not requested. Whether `pi`/`opencode`
324
+ themselves actually have the blocking-tool-call capability needed for
325
+ `wait` to be a genuine reactive trigger for them, same as `agy`, **hasn't
326
+ been verified** — only assumed to work similarly, no end-to-end test done
327
+ yet like the one run against `agy`.
328
+
329
+ **Found a pre-existing `agent-peer` skill already installed for agy**
330
+ (`~/.gemini/antigravity/skills/agent-peer/SKILL.md`) — this is what had been
331
+ letting `agy` "know" how to use `agent-peer` on its own without ever being
332
+ taught manually in this session (GEMINI.md auto-loads skills based on
333
+ description). This skill turned out to be the **original source** of 2 bugs
334
+ fixed this session: the old SOP explicitly told it to always use
335
+ `--name antigravity` (causing `antigravity-2`/`-3` pileup), and the old
336
+ mandatory rule told it to always launch a new `wait` before ending a turn
337
+ without ever checking whether the old one was still alive (exactly the
338
+ 2-wait race scenario found earlier). **Already updated:** removed all
339
+ `--name antigravity` hardcoding (replaced with auto-detect), fixed the
340
+ `wait` description (now instant backlog-merge, not just "next message"),
341
+ added guidance about the new lock-error ("already running" = normal, not
342
+ something to retry). Added a "Trigger explicitly `/agent-peer intro`" line
343
+ at the top — user confirmed `/agent-peer` in agy really does auto-list the
344
+ skill by name.
345
+
346
+ **Correction:** briefly recommended `--timeout 60` in the mandatory-standby
347
+ rule — the user rejected it, correctly. Since `wait` runs as a background
348
+ task that triggers wakeup the moment the process exits, a bounded timeout
349
+ would make it exit every 60 seconds with nothing to report and need
350
+ relaunching — that's polling in disguise, contradicting `wait`'s own
351
+ purpose ("never poll"). My lock-safety argument was also wrong — `flock`
352
+ releases automatically at the OS level no matter why the process died, it
353
+ doesn't need a timeout for that. Sequential sessions waiting on a peer for
354
+ minutes/hours are a valid case, let `wait` block without a limit. Already
355
+ revised: the mandatory rule is now `agent-peer wait` with no `--timeout`;
356
+ the flag stays documented as an option available for other, non-background
357
+ uses.
358
+
359
+ **Additional findings from agy's exploration**
360
+ (`docs/agent-peer-weaknesses-report.md`, cross-checked against source —
361
+ valid): inbox/cursor files not `chmod 0600` (only the socket & key file are
362
+ protected); no `SO_PEERCRED` verification on the socket auth (only string
363
+ token matching); race condition from non-atomic writes in `append_inbox`
364
+ (no `flock`); cursor doesn't get reset on `agent-peer inbox --clear`; stale
365
+ sessions also happen if a listener gets `SIGKILL`/crashes (not just when a
366
+ UI session is closed — overlaps with [[stale-listener-detection]] but a
367
+ different trigger). None of this list has been worked on yet — pure
368
+ findings, waiting on the user's prioritization.
369
+
370
+ **Bug found by the user while testing `pi` live:** `listener.py` hardcodes
371
+ `self.cwd = cwd or os.path.expanduser("~/projects")` — so the CWD column in
372
+ `agent-peer list` always shows `~/projects` no matter which directory
373
+ `agent-peer listen` was actually run from. Fix: switched to `os.getcwd()`.
374
+ This is in core code (shared across every harness), so it automatically
375
+ applies to agy/pi/opencode at once, no separate per-harness work needed.
376
+ Tested in sandbox (cwd now correctly points to the real working directory).
377
+ Live sessions that were already registered wrong (`antigravity-test` PID
378
+ 52285, `pi-98661` PID 512) were manually patched to
379
+ `/Users/rg/projects/agent-peer` to match their current working context.
380
+
381
+ ## 2026-09-17 — Watch accuracy test, git history bootstrap
382
+
383
+ **`agent-peer watch` accuracy validated live.** User relies on `watch` to
384
+ monitor the 3 test harnesses and was worried it might be inaccurate or drop
385
+ something. Reviewed `logs.py` and found it had never been specifically
386
+ audited by any of the 4 reviewers. Two real caveats found (not bugs in
387
+ current normal operation, but worth knowing): (1) a sender/recipient
388
+ already dead by the time you view the log shows as `pid-<N>` instead of a
389
+ name; (2) `extract_recipient_info`'s "legacy inference" fallback
390
+ (`logs.py:144-154`) hardcodes THIS session's own dev PIDs (72769/29258/
391
+ 71277) as magic numbers — dead weight for anything logged going forward,
392
+ but a landmine if those PIDs ever get reused by an unrelated process later
393
+ (confirmed PID reuse is a real possibility on macOS). Recommended removing
394
+ it; not yet done.
395
+
396
+ Ran a live 3-way triangle test (agy → opencode, pi → antigravity-test,
397
+ opencode → pi, all reporting back to Claude) while capturing
398
+ `agent-peer watch --raw` in the background. Cross-checked every reported
399
+ message verbatim against the captured JSONL — **100% match, byte-for-byte,
400
+ correct sender/recipient attribution, zero loss.** Side finding: `watch`
401
+ without a `-s <name>` filter is a genuinely global feed across every
402
+ session on the machine — it also picked up unrelated real production
403
+ traffic from a different project (`claude-81449`/ottoshare-factory) running
404
+ in parallel. Worth using `-s <name>` if the goal is only to watch specific
405
+ sessions.
406
+
407
+ **Gotcha hit while starting the watch process:** mixed `run_in_background`
408
+ (the tool's own backgrounding) with a manual `&` plus trailing foreground
409
+ commands in the same script — the tool considered the whole invocation
410
+ "completed" once the trailing commands finished, even though the actual
411
+ `agent-peer watch` child process (backgrounded via `&`) kept running,
412
+ reparented to launchd. Not a crash, just confusing: don't mix the two
413
+ backgrounding mechanisms in one call — launch the persistent command alone
414
+ via `run_in_background`, nothing else in the same invocation.
415
+
416
+ **Kicked off a 4-way collaborative review** (agy, pi, opencode, Claude), each
417
+ independently exploring `agent-peer` from their own harness's perspective
418
+ and writing findings to `.dev/reviews/<name>-review.md`. All 4 completed;
419
+ cross-confirmed findings across 2+ reviewers were treated as highest
420
+ confidence. One direct contradiction surfaced and got resolved: agy's
421
+ review initially claimed `/agent-peer` wasn't a real slash command in
422
+ Antigravity, then agy itself retracted that after re-verifying live — the
423
+ original user claim ("it works") was correct. Corrected in
424
+ `agy-review.md` with a strikethrough + note (kept, not deleted, so the
425
+ investigation trail survives).
426
+
427
+ Triaged the ~20 findings from the 4 reviews into 10 candidate tasks (see the
428
+ M1 entry above); tasks 0001-0003 were implemented and tested directly.
429
+
430
+ **Skill templates shipped into the repo** (`skills/<harness>/`) — previously
431
+ only lived in each user's global config directory
432
+ (`~/.gemini/...`/`~/.pi/...`/`~/.config/opencode/...`), so cloning this repo
433
+ gave no way to reconstruct them. Each harness folder ships its current
434
+ `SKILL.md` plus a `README.md` covering that harness's own install location,
435
+ frontmatter convention, and trigger mechanism — verified against each
436
+ harness's own source/docs during this session, not assumed (the three
437
+ turned out to genuinely differ: auto-load + `/agent-peer` slash for agy/pi,
438
+ explicit `skill()` tool call only for opencode, no slash command at all).
439
+
440
+ **Git history bootstrapped.** All of this session's work had been sitting
441
+ uncommitted the entire time. Committed in 4 logical commits (code / docs+
442
+ reviews / handwalk scaffolding / opencode's report), renamed `master` to
443
+ `main`, created a private GitHub repo (`mkhuda/agent-peer`) via `gh`, and
444
+ pushed. No `Co-Authored-By` line on any commit per the user's standing
445
+ instruction.
446
+
447
+ ## Dead ends and rejected approaches (kept so they aren't retried)
448
+
449
+ - **Auto-spawning `listen` from inside `wait`** when no listener is found —
450
+ rejected by the user: risks silently piling up listeners, the exact
451
+ "stale listener" failure mode already diagnosed separately. Replaced with
452
+ a stderr warning that doesn't create any new process.
453
+ - **Detecting a "detached"/orphaned listener via TTY** (`??`) — disproven:
454
+ a currently-in-use listener has an identical TTY/PPID/STAT to a genuinely
455
+ stale one. No OS-level signal distinguishes them.
456
+ - **Checking the listener's own parent process for liveness** as a
457
+ detached-session signal — disproven: both listeners were already
458
+ reparented to launchd (PPID=1) immediately on spawn, and there was no
459
+ Antigravity app process visible locally at all to check against.
460
+ - **Recommending a bounded `--timeout` for the mandatory background-standby
461
+ pattern** — rejected by the user: turns `wait` into disguised polling for
462
+ any harness that re-invokes on background-task exit, and the lock-safety
463
+ argument for it was wrong (`flock` already releases on process death
464
+ regardless of cause).
@@ -0,0 +1,5 @@
1
+ # Log
2
+
3
+ Append-only, newest at the bottom, one line per mistake worth not repeating. Dated, and
4
+ signed by whoever made it. Not for blame: a rule that lives in a message is not a rule.
5
+
@@ -0,0 +1,74 @@
1
+ # Review & Audit of `agent-peer` + `SKILL.md` from Google Antigravity Harness (AGY) Perspective
2
+
3
+ **Author:** Google Antigravity Agent (AGY)
4
+ **Date:** September 17, 2026
5
+ **Session Context:** Real-world evaluation of message delivery, backlog cursor, lock concurrency, auth fixes, multi-harness relay test (AGY -> PI -> OPENCODE -> AGY), and auto-session name detection.
6
+
7
+ ---
8
+
9
+ ## 1. Executive Summary
10
+
11
+ In a series of live multi-harness tests, `agent-peer` demonstrated excellent core IPC (inter-process communication) functionality between agents:
12
+ - Message delivery latency via Unix Domain Sockets was consistently **< 200ms** (~165-174ms).
13
+ - The **unread cursor & backlog merging** feature successfully captured messages arriving while the agent was busy executing other tasks without any message loss.
14
+ - The exclusive `fcntl.flock` lock successfully prevented duplicate *race conditions* for the same session name.
15
+
16
+ However, deep evaluation from the **Google Antigravity** harness perspective revealed several **ambiguities in `SKILL.md`**, **edge cases in the auto-detection process**, and **potential concurrency issues with subagents/multi-session setups** that are critical to address.
17
+
18
+ ---
19
+
20
+ ## 2. Ambiguities & Issues in `SKILL.md`
21
+
22
+ ### 2.1 ~~The `/agent-peer intro` Command is Incompatible with Antigravity CLI~~ — WITHDRAWN
23
+ * **Initial Claim:** Line 8 stated `Trigger explicitly any time with /agent-peer intro to load this skill`, which was initially deemed incompatible with Antigravity's built-in slash commands.
24
+ * **Correction (re-verified in this session):** `/agent-peer` was proven to be fully supported & officially registered once the skill is installed — Antigravity UI integration for custom skill slash commands works seamlessly. Claim 2.1 above is **invalid**, left struck-through (not deleted) as an investigation trail.
25
+
26
+ ### 2.2 Confusion Between Role Alias vs Registered Session Name
27
+ * **Issue in SKILL.md:** Section B instructs `agent-peer send <peer-name> "[fyi from antigravity]: ..."` and permits `--sender`.
28
+ * **Harness Reality:** When a sender executes `agent-peer send claude-test "..."`, the command fails (`ValueError: Session 'claude-test' not found`) if the receiving listener is registered in `~/.claude/sessions/` with a physical name like `agent-peer-e4`. The `--sender` parameter in the sender frame only alters the `from` string inside the payload, without registering a name alias in the registry.
29
+ * **Recommendation:** Clarify in `SKILL.md` that `<peer-name>` **MUST** match the `SESSION NAME` column output by `agent-peer list`, not a functional/role alias of the agent (unless that session was explicitly started with `--name <role>`).
30
+
31
+ ---
32
+
33
+ ## 3. Auto-Detection Edge Cases (`detect_harness_identity`)
34
+
35
+ ### 3.1 Subagent & Parallel Task Lock Collision
36
+ * **Auto-Detect Mechanism:** `detect_harness_identity()` traverses up the process tree (`os.getppid()`) looking for the first non-generic process name. For Antigravity, it finds `agy` with the main PID (e.g. `PID 33402`), creating the name `agy-33402`.
37
+ * **Issue with Subagent/Child Tasks:**
38
+ If an Antigravity agent spawns a subagent (e.g. via `invoke_subagent` or background worker processes within the same workspace), all subagents inherit the **same** parent `agy` process (`PID 33402`).
39
+ As a result:
40
+ 1. All subagents are auto-detected with the **exact same** name (`agy-33402`).
41
+ 2. When subagent A and subagent B both call `agent-peer wait`, subagent B fails immediately due to the `fcntl.flock` lock on `~/.agent-peer/locks/agy-33402.lock` ("already running").
42
+ 3. Messages intended for a specific subagent get mixed up in `agy-33402`'s inbox.
43
+ * **Recommendation:** Add a rule in `SKILL.md` stating that if an agent spawns separate subagents or parallel tasks requiring independent communication, subagents **MUST** specify an explicit name (e.g., `agent-peer wait --name agy-subagent-1`).
44
+
45
+ ### 3.2 Accumulated File Locks & Multi-Name Standby Leak
46
+ * **Antigravity Harness Reality:**
47
+ Antigravity manages background processes via `run_command` with `WaitMsBeforeAsync: 1000`. Over a long session, an agent might switch standby names from `--name antigravity-test` to auto-detected `agy-33402`.
48
+ * **Effect:** `task-58` continues to hold `antigravity-test.lock`, while `task-84` holds `agy-33402.lock`. Two separate listeners/waiters run concurrently in the background without releasing locks because their session names differ.
49
+ * **Recommendation:** `agent-peer` should include an `agent-peer stop` command or automatic lock cleanup if a previous listener under the same parent PID is detected with an old name alias.
50
+
51
+ ---
52
+
53
+ ## 4. Codebase Security & Reliability Evaluation (Technical Findings Summary)
54
+
55
+ 1. **Inbox File Permissions (`~/.agent-peer/inbox.jsonl`):**
56
+ The `~/.agent-peer/` directory and inbox files are created with default umask permissions (`0644`). In a multi-user Unix environment, other local users can read inter-agent message contents. *Fix: Enforce `chmod 0700` on the directory and `0600` on inbox/cursor files.*
57
+ 2. **Missing `SO_PEERCRED` Socket Verification:**
58
+ `listener.py` verifies the string authentication token, but does not verify the sender's OS socket credentials (`SO_PEERCRED`).
59
+ 3. **Non-Atomic File Append (`append_inbox`):**
60
+ `append_inbox()` performs message appending without `fcntl.flock`. Under high load from multiple socket threads, JSONL writes can suffer line interleaving.
61
+ 4. **Notification Sanitization (AppleScript / Terminal Notifier):**
62
+ The regex `clean_snippet = re.sub(r'<[^>]+>', '', content)` in `listener.py:216` strips HTML/XML tags. If an agent sends JSX/HTML code snippets (like `<div>`), characters inside those tags get accidentally deleted from the notification display.
63
+
64
+ ---
65
+
66
+ ## 5. Conclusion & SKILL.md Improvement Checklist
67
+
68
+ | Item | Status | Recommended Action |
69
+ |---|---|---|
70
+ | Slash Command `/agent-peer` | Withdrawn | Retain custom slash command support documentation in Antigravity UI |
71
+ | Peer Name Resolution | Error-prone | Enforce that `<peer-name>` must strictly match `agent-peer list` output |
72
+ | Subagent Isolation | High Risk | Document mandatory `--name <subagent-id>` requirement for subagents |
73
+ | File & Socket Security | Medium Risk | Enforce `chmod 0600` on inbox/cursors & lock files |
74
+ | Atomic File Write | Medium Risk | Implement `fcntl.flock` on `append_inbox()` |