agent-yes 1.276.0 → 1.277.1

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 (76) hide show
  1. package/README.md +10 -9
  2. package/agent-yes.config.schema.json +4 -1
  3. package/default.config.yaml +49 -0
  4. package/dist/{SUPPORTED_CLIS-G5YhpzKV.js → SUPPORTED_CLIS-DMA2T-LF.js} +2 -2
  5. package/dist/{SUPPORTED_CLIS-D-hylNcp.js → SUPPORTED_CLIS-DzVEFVKI.js} +3 -3
  6. package/dist/{agentShare-BmQZ96CJ.js → agentShare-DqT9USG5.js} +3 -3
  7. package/dist/{callback-BEgUEMY3.js → callback-D60U9zFd.js} +3 -3
  8. package/dist/{callback-B2oH6mCo.js → callback-PKIAkhNq.js} +3 -3
  9. package/dist/cli.js +11 -11
  10. package/dist/cmdDsh-BySPA9hP.js +45 -0
  11. package/dist/{cmdPs-E6fcHruY.js → cmdPs-BeUkjSOH.js} +3 -3
  12. package/dist/{cwdConflictWarn-CqN5pJ-7.js → cwdConflictWarn-DIPD6HH5.js} +1 -1
  13. package/dist/{cwdPassthroughHint-DxdqgwEN.js → cwdPassthroughHint-C0tfZlgZ.js} +1 -1
  14. package/dist/dsh-legacy-yes.js +10 -0
  15. package/dist/dsh-tui-yes.js +10 -0
  16. package/dist/dsh-yes.js +10 -0
  17. package/dist/{expose-DTxYWQeW.js → expose-Ds9aUFsq.js} +1 -1
  18. package/dist/{forkNested-CZAvQj0y.js → forkNested-Dt5XP8ZH.js} +1 -1
  19. package/dist/{hist-QkZZBp-y.js → hist-DSYaJEd3.js} +1 -1
  20. package/dist/index.js +2 -2
  21. package/dist/{initMsg-DAwRS89d.js → initMsg-CeF4QNOM.js} +1 -1
  22. package/dist/{initMsg-C8ISz_C7.js → initMsg-DejbYYjX.js} +1 -1
  23. package/dist/{nodeRuntime-C_LtZrpR.js → nodeRuntime-_zeWDISM.js} +1 -1
  24. package/dist/{notifyDaemon-D_x5ow8y.js → notifyDaemon-Bw70QCkC.js} +3 -3
  25. package/dist/{openBrowser-B50Lhaob.js → openBrowser-BNP8xByu.js} +1 -1
  26. package/dist/{parentLink-svMgYhEi.js → parentLink-Bv8ihRcu.js} +1 -1
  27. package/dist/{parentLink-i817PzXK.js → parentLink-HWunrH1s.js} +1 -1
  28. package/dist/{parentPingLoop-DP2y906e.js → parentPingLoop-DJAPbp6D.js} +4 -4
  29. package/dist/{remotes-gAI6-nv9.js → remotes-BlHTR9ow.js} +1 -1
  30. package/dist/{remotes-Cdlaj7AP.js → remotes-b6reVXzo.js} +2 -2
  31. package/dist/{rustBinary-BwgA8XyI.js → rustBinary-CxtSgRSg.js} +2 -2
  32. package/dist/{rustBinary-bXjmCsim.js → rustBinary-DQHcq1Wr.js} +2 -2
  33. package/dist/{schedule-CdEUIaEV.js → schedule-BI7-Pa2F.js} +6 -6
  34. package/dist/{serve-CvLw3YZx.js → serve-Bfxc3VWQ.js} +26 -26
  35. package/dist/{setup-BRyFMtpv.js → setup-Cp31FALW.js} +4 -4
  36. package/dist/{spawnGate-DtC_ffme.js → spawnGate-B2hA_2NQ.js} +2 -2
  37. package/dist/{spawnGate-CdewnL8m.js → spawnGate-FLwTAPW6.js} +1 -1
  38. package/dist/{subcommands-CqBONdKo.js → subcommands-CjgHXjZd.js} +2 -2
  39. package/dist/{subcommands-BtRA2slB.js → subcommands-Cohq91Dx.js} +27 -22
  40. package/dist/{systemPathLink-Df_pstm2.js → systemPathLink-Dl2gJqeg.js} +1 -1
  41. package/dist/{terminal-8jfLRLtF.js → terminal-8ipst6Wf.js} +2 -2
  42. package/dist/{todoCli-Cf0EHvfy.js → todoCli-MWrejmdM.js} +1 -1
  43. package/dist/{tray-BeIs4ffu.js → tray-BhPVpP_r.js} +1 -1
  44. package/dist/{trayApp-CnJiwYjb.js → trayApp-CL_iZpnR.js} +2 -2
  45. package/dist/trayApp-DaekNsVe.js +5 -0
  46. package/dist/{ts-CEtUijwR.js → ts-RBN1OMQ_.js} +5 -5
  47. package/dist/{versionChecker-CL6Dvn--.js → versionChecker-F_WiFsVg.js} +2 -2
  48. package/dist/{webrtcRemote-CIbq5Hdt.js → webrtcRemote-H9Hok0_R.js} +1 -1
  49. package/dist/{widget-DvSx4hyW.js → widget-DTramHv8.js} +3 -3
  50. package/dist/{workspaceConfig-DctWuxUP.js → workspaceConfig-D6QndOCz.js} +1 -1
  51. package/dist/{ws-XngU-GGe.js → ws-dgltfu3A.js} +3 -3
  52. package/dist/{ws-B830G79O.js → ws-sf8JPYPq.js} +3 -3
  53. package/lab/ui/index.html +2 -1
  54. package/lab/ui/lab/build.ts +5 -4
  55. package/lab/ui/lab/index.md +14 -14
  56. package/lab/ui/lab/posts/2026-06-15-e2ee-share-links.html +128 -132
  57. package/lab/ui/lab/posts/2026-07-22-ay-callback.html +214 -167
  58. package/lab/ui/lab/posts/2026-08-11-an-address-space-for-agents.md +9 -9
  59. package/lab/ui/lab/posts/2026-08-11-no-daemon-owns-your-agents.md +13 -13
  60. package/lab/ui/lab/posts/2026-08-11-slack-term-safe-writes.md +26 -26
  61. package/lab/ui/lab/posts/2026-08-11-the-terminal-is-the-api.md +10 -10
  62. package/lab/ui/landing.html +2 -1
  63. package/package.json +4 -1
  64. package/ts/buildRustArgs.spec.ts +3 -0
  65. package/ts/cmdDsh.spec.ts +90 -0
  66. package/ts/cmdDsh.ts +62 -0
  67. package/ts/histStore.spec.ts +8 -4
  68. package/ts/histStore.ts +2 -7
  69. package/ts/identity.spec.ts +4 -1
  70. package/ts/initMsg.spec.ts +12 -2
  71. package/ts/invokedCli.spec.ts +1 -0
  72. package/ts/postbuild.ts +11 -8
  73. package/ts/subcommands.ts +6 -0
  74. package/ts/systemPathLink.spec.ts +12 -2
  75. package/ts/systemPathLink.ts +5 -1
  76. package/dist/trayApp-CUuEodHT.js +0 -5
@@ -3,22 +3,23 @@ title: ay callback — let readers message your agent from any web page
3
3
  tag: Feature
4
4
  summary: A signed, send-only, single-agent capability plus a one-script embed widget: report pages that can talk back, with a required expiry and a revoke kill-switch — never your serve token.
5
5
  ---
6
+
6
7
  <p class="lede">
7
- Your agent just published a report. The person reading it has a question. Until now the
8
- answer was "find the console, find the right terminal, type there." Now the report page
9
- itself can carry a tiny widget that delivers the reader's message straight into that agent's
10
- terminal — through a signed, send-only, single-agent capability that expires on a date you
11
- chose, and never through your master token.
12
- </p>
8
+ Your agent just published a report. The person reading it has a question. Until now the answer was
9
+ "find the console, find the right terminal, type there." Now the report page itself can carry a
10
+ tiny widget that delivers the reader's message straight into that agent's terminal — through a
11
+ signed, send-only, single-agent capability that expires on a date you chose, and never through
12
+ your master token.
13
+ </p>
13
14
 
14
- <h2 id="what">What it does <a class="anchor" href="#what">#</a></h2>
15
- <p>
16
- <code>ay callback</code> mints a capability for <strong>one agent</strong> and prints a
17
- self-contained HTML snippet — one inline <code>&lt;script&gt;</code>, zero external
18
- requests. Paste it into any page your agent publishes (a status dashboard, a nightly report,
19
- a postmortem) and readers get a floating button:
20
- </p>
21
- <pre><code>$ ay callback --expires 7d
15
+ <h2 id="what">What it does <a class="anchor" href="#what">#</a></h2>
16
+ <p>
17
+ <code>ay callback</code> mints a capability for <strong>one agent</strong> and prints a
18
+ self-contained HTML snippet — one inline <code>&lt;script&gt;</code>, zero external requests.
19
+ Paste it into any page your agent publishes (a status dashboard, a nightly report, a postmortem)
20
+ and readers get a floating button:
21
+ </p>
22
+ <pre><code>$ ay callback --expires 7d
22
23
  callback 3b6f23e7 → claude #46555 @ ~/ws/reports
23
24
  expires: 2026-07-29T10:57:01.398Z (7d)
24
25
  endpoint: https://x1a2b3c.agent-yes.com/cb/cb1.eyJpZCI6…
@@ -26,175 +27,221 @@ revoke: ay callback revoke 3b6f23e7
26
27
 
27
28
  ── embed snippet ──────────────────────────────
28
29
  &lt;script&gt; … &lt;/script&gt;</code></pre>
29
- <p>Here is what the widget feels like (demo only — this one talks to nobody):</p>
30
- <div class="demo" id="cb-demo">
31
- Imagine this box is your agent's published report page…
32
- <button class="fab" id="cb-demo-fab">💬 Message the agent</button>
33
- <div class="modal" id="cb-demo-modal">
34
- <strong>Message the agent</strong>
35
- <textarea id="cb-demo-ta" placeholder="Your message to the agent…"></textarea>
36
- <button id="cb-demo-send">Send</button>
37
- <div class="status" id="cb-demo-status"></div>
38
- </div>
39
- </div>
40
- <p>
41
- When a reader hits <em>Send</em>, the message lands in the agent's terminal wrapped in an
42
- explicit "untrusted visitor" frame, and in its inbox. One way, by design — the visitor never
43
- sees the terminal.
44
- </p>
30
+ <p>Here is what the widget feels like (demo only — this one talks to nobody):</p>
31
+ <div class="demo" id="cb-demo">
32
+ Imagine this box is your agent's published report page…
33
+ <button class="fab" id="cb-demo-fab">💬 Message the agent</button>
34
+ <div class="modal" id="cb-demo-modal">
35
+ <strong>Message the agent</strong>
36
+ <textarea id="cb-demo-ta" placeholder="Your message to the agent…"></textarea>
37
+ <button id="cb-demo-send">Send</button>
38
+ <div class="status" id="cb-demo-status"></div>
39
+ </div>
40
+ </div>
41
+ <p>
42
+ When a reader hits <em>Send</em>, the message lands in the agent's terminal wrapped in an explicit
43
+ "untrusted visitor" frame, and in its inbox. One way, by design — the visitor never sees the
44
+ terminal.
45
+ </p>
45
46
 
46
- <h2 id="capability">
47
- The capability is a hash, not a key <a class="anchor" href="#capability">#</a>
48
- </h2>
49
- <p>
50
- The obvious-but-wrong implementation is to put your daemon's serve token in the snippet.
51
- That token is a master key: whoever holds it can read every terminal, send to every agent,
52
- spawn and kill. Pasting it into a public page is game over.
53
- </p>
54
- <p>
55
- So the snippet carries a <strong>capability token</strong> instead — three dot-separated
56
- parts, the last one an HMAC-SHA256 over the rest:
57
- </p>
58
- <pre><code>cb1 . base64url({ "id": "3b6f23e7",
47
+ <h2 id="capability">
48
+ The capability is a hash, not a key <a class="anchor" href="#capability">#</a>
49
+ </h2>
50
+ <p>
51
+ The obvious-but-wrong implementation is to put your daemon's serve token in the snippet. That
52
+ token is a master key: whoever holds it can read every terminal, send to every agent, spawn and
53
+ kill. Pasting it into a public page is game over.
54
+ </p>
55
+ <p>
56
+ So the snippet carries a <strong>capability token</strong> instead — three dot-separated parts,
57
+ the last one an HMAC-SHA256 over the rest:
58
+ </p>
59
+ <pre><code>cb1 . base64url({ "id": "3b6f23e7",
59
60
  "agent": "4d65a096e983", // exactly one agent_id
60
61
  "exp": 1785322621398 }) // hard expiry, unix ms
61
62
  . base64url(HMAC-SHA256(secret, "cb1." + payload))</code></pre>
62
- <p>
63
- The scope lives <em>inside the signed payload</em>. That has three consequences worth
64
- spelling out:
65
- </p>
66
- <table>
67
- <tr>
68
- <th>Attack</th>
69
- <th>Result</th>
70
- </tr>
71
- <tr>
72
- <td>Edit the payload to point at another agent, or push the expiry out</td>
73
- <td><span class="no">✗</span> signature no longer verifies — the daemon answers 404</td>
74
- </tr>
75
- <tr>
76
- <td>Delete the daemon's bookkeeping to "resurrect" an expired token</td>
77
- <td>
78
- <span class="no">✗</span> expiry is in the token itself, not in local state — still 410
79
- </td>
80
- </tr>
81
- <tr>
82
- <td>Steal the token from the page source</td>
83
- <td>
84
- <span class="ok">✓</span> you can do exactly what the page could already do: send
85
- rate-limited text to one agent, until the expiry the owner chose
86
- </td>
87
- </tr>
88
- </table>
89
- <p>
90
- The HMAC secret is minted on first use into <code>~/.agent-yes/.callback-secret</code>
91
- (0600), deliberately separate from the serve token — rotating one never invalidates the
92
- other.
93
- </p>
63
+ <p>
64
+ The scope lives <em>inside the signed payload</em>. That has three consequences worth spelling
65
+ out:
66
+ </p>
67
+ <table>
68
+ <tr>
69
+ <th>Attack</th>
70
+ <th>Result</th>
71
+ </tr>
72
+ <tr>
73
+ <td>Edit the payload to point at another agent, or push the expiry out</td>
74
+ <td><span class="no">✗</span> signature no longer verifies — the daemon answers 404</td>
75
+ </tr>
76
+ <tr>
77
+ <td>Delete the daemon's bookkeeping to "resurrect" an expired token</td>
78
+ <td><span class="no">✗</span> expiry is in the token itself, not in local state — still 410</td>
79
+ </tr>
80
+ <tr>
81
+ <td>Steal the token from the page source</td>
82
+ <td>
83
+ <span class="ok">✓</span> you can do exactly what the page could already do: send rate-limited
84
+ text to one agent, until the expiry the owner chose
85
+ </td>
86
+ </tr>
87
+ </table>
88
+ <p>
89
+ The HMAC secret is minted on first use into <code>~/.agent-yes/.callback-secret</code>
90
+ (0600), deliberately separate from the serve token — rotating one never invalidates the other.
91
+ </p>
94
92
 
95
- <h2 id="expiry">
96
- --expires is required. There is no "never" <a class="anchor" href="#expiry">#</a>
97
- </h2>
98
- <p>
99
- Every capability API eventually grows a default lifetime, and every default lifetime
100
- eventually becomes "forever, because nobody thinks about it." We refuse the default:
101
- </p>
102
- <pre><code>$ ay callback
93
+ <h2 id="expiry">
94
+ --expires is required. There is no "never" <a class="anchor" href="#expiry">#</a>
95
+ </h2>
96
+ <p>
97
+ Every capability API eventually grows a default lifetime, and every default lifetime eventually
98
+ becomes "forever, because nobody thinks about it." We refuse the default:
99
+ </p>
100
+ <pre><code>$ ay callback
103
101
  ay callback: --expires is required (e.g. --expires 7d) —
104
102
  an embed capability must carry an explicit lifetime</code></pre>
105
- <p>
106
- <code>--expires</code> takes <code>30m</code>, <code>12h</code>, <code>7d</code>,
107
- <code>2w</code> — and caps at 365 days. There is no <code>--expires never</code> and there
108
- never will be. The point is a moment of deliberate friction: whoever pastes a capability
109
- into a public page must answer "how long should this live?" while they still remember the
110
- page exists. When it lapses, the widget doesn't fail silently — it tells the reader the link
111
- expired and to ask the owner for a fresh one.
112
- </p>
113
- <p>For everything before the deadline, there's the kill switch:</p>
114
- <pre><code>$ ay callback ls
103
+ <p>
104
+ <code>--expires</code> takes <code>30m</code>, <code>12h</code>, <code>7d</code>,
105
+ <code>2w</code> — and caps at 365 days. There is no <code>--expires never</code> and there never
106
+ will be. The point is a moment of deliberate friction: whoever pastes a capability into a public
107
+ page must answer "how long should this live?" while they still remember the page exists. When it
108
+ lapses, the widget doesn't fail silently — it tells the reader the link expired and to ask the
109
+ owner for a fresh one.
110
+ </p>
111
+ <p>For everything before the deadline, there's the kill switch:</p>
112
+ <pre><code>$ ay callback ls
115
113
  3b6f23e7 6d left → claude #46555 @ ~/ws/reports
116
114
  $ ay callback revoke 3b6f23e7
117
115
  revoked 3b6f23e7</code></pre>
118
116
 
119
- <h2 id="hardening">
120
- A public route that leaks nothing <a class="anchor" href="#hardening">#</a>
121
- </h2>
122
- <p>
123
- <code>POST /cb/&lt;cap&gt;</code> is the daemon's only unauthenticated write route, so it is
124
- deliberately paranoid:
125
- </p>
126
- <ul>
127
- <li>
128
- <strong>Info-tight responses.</strong> The page learns <code>ok</code>,
129
- <code>expired</code>, or a generic failure — never a pid, a cwd, a hostname, or whether a
130
- given agent exists. Malformed and forged tokens are indistinguishable 404s.
131
- </li>
132
- <li>
133
- <strong>Untrusted framing.</strong> Visitor text is stripped of control characters (no
134
- smuggled escape sequences, no fake Enter) and wrapped in an
135
- <code>&lt;ay-callback&gt;</code> block that names it an untrusted visitor message — the
136
- agent is told to treat it as data, not instructions.
137
- </li>
138
- <li>
139
- <strong>Flood control.</strong> Per-capability sliding-window rate limit and a 4&nbsp;KB
140
- body cap. A public page can nudge your agent; it cannot bury it.
141
- </li>
142
- <li>
143
- <strong>One-way.</strong> The route writes to the agent's stdin and records the inbox
144
- entry. It never reads the terminal back — a public embed must not become a viewport into
145
- your machine.
146
- </li>
147
- </ul>
148
- <div class="note">
149
- Two-way chat — the widget showing the agent's reply — is the obvious next step, and the
150
- design line is already drawn: the page will only ever see replies the agent
151
- <em>explicitly</em> addresses to that callback, never the raw terminal.
152
- </div>
117
+ <h2 id="hardening">A public route that leaks nothing <a class="anchor" href="#hardening">#</a></h2>
118
+ <p>
119
+ <code>POST /cb/&lt;cap&gt;</code> is the daemon's only unauthenticated write route, so it is
120
+ deliberately paranoid:
121
+ </p>
122
+ <ul>
123
+ <li>
124
+ <strong>Info-tight responses.</strong> The page learns <code>ok</code>, <code>expired</code>, or
125
+ a generic failure — never a pid, a cwd, a hostname, or whether a given agent exists. Malformed
126
+ and forged tokens are indistinguishable 404s.
127
+ </li>
128
+ <li>
129
+ <strong>Untrusted framing.</strong> Visitor text is stripped of control characters (no smuggled
130
+ escape sequences, no fake Enter) and wrapped in an <code>&lt;ay-callback&gt;</code> block that
131
+ names it an untrusted visitor message — the agent is told to treat it as data, not instructions.
132
+ </li>
133
+ <li>
134
+ <strong>Flood control.</strong> Per-capability sliding-window rate limit and a 4&nbsp;KB body
135
+ cap. A public page can nudge your agent; it cannot bury it.
136
+ </li>
137
+ <li>
138
+ <strong>One-way.</strong> The route writes to the agent's stdin and records the inbox entry. It
139
+ never reads the terminal back — a public embed must not become a viewport into your machine.
140
+ </li>
141
+ </ul>
142
+ <div class="note">
143
+ Two-way chat — the widget showing the agent's reply — is the obvious next step, and the design
144
+ line is already drawn: the page will only ever see replies the agent
145
+ <em>explicitly</em> addresses to that callback, never the raw terminal.
146
+ </div>
153
147
 
154
- <h2 id="try">Try it <a class="anchor" href="#try">#</a></h2>
155
- <pre><code>$ npm i -g agent-yes
148
+ <h2 id="try">Try it <a class="anchor" href="#try">#</a></h2>
149
+ <pre><code>$ npm i -g agent-yes
156
150
  $ ay serve # local daemon
157
151
  $ ay expose … # or put the daemon on a public URL first
158
152
  $ ay callback --expires 7d --title "Ask the report bot"</code></pre>
159
- <p>
160
- Paste the printed snippet at the end of anything your agent publishes. Ship the report; keep
161
- the conversation.
162
- </p>
153
+ <p>
154
+ Paste the printed snippet at the end of anything your agent publishes. Ship the report; keep the
155
+ conversation.
156
+ </p>
163
157
  <style>
164
158
  /* Inert demo replica of the widget the snippet renders. */
165
- .demo{position:relative;background:var(--card);border:1px dashed var(--border);
166
- border-radius:12px;min-height:180px;margin:18px 0;padding:12px 16px;color:var(--muted);font-size:.9em}
167
- .demo .fab{position:absolute;right:16px;bottom:16px;padding:10px 14px;border-radius:20px;
168
- border:none;background:#1a7f37;color:#fff;font:13px system-ui;cursor:pointer;
169
- box-shadow:0 2px 8px rgba(0,0,0,.3)}
170
- .demo .modal{position:absolute;right:16px;bottom:64px;width:min(300px,80%);background:var(--bg);
171
- color:var(--fg);border:1px solid var(--border);border-radius:8px;padding:12px;font:13px system-ui;
172
- box-shadow:0 4px 16px rgba(0,0,0,.25);display:none;text-align:left}
173
- .demo .modal textarea{width:100%;height:64px;box-sizing:border-box;font:inherit;margin:8px 0;
174
- resize:vertical;background:var(--card);color:var(--fg);border:1px solid var(--border);
175
- border-radius:6px;padding:6px}
176
- .demo .modal button{padding:6px 12px;border:none;border-radius:6px;background:#1a7f37;color:#fff;
177
- cursor:pointer;font:inherit}
178
- .demo .status{min-height:16px;font-size:12px;color:var(--muted);margin-top:6px}
159
+ .demo {
160
+ position: relative;
161
+ background: var(--card);
162
+ border: 1px dashed var(--border);
163
+ border-radius: 12px;
164
+ min-height: 180px;
165
+ margin: 18px 0;
166
+ padding: 12px 16px;
167
+ color: var(--muted);
168
+ font-size: 0.9em;
169
+ }
170
+ .demo .fab {
171
+ position: absolute;
172
+ right: 16px;
173
+ bottom: 16px;
174
+ padding: 10px 14px;
175
+ border-radius: 20px;
176
+ border: none;
177
+ background: #1a7f37;
178
+ color: #fff;
179
+ font: 13px system-ui;
180
+ cursor: pointer;
181
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
182
+ }
183
+ .demo .modal {
184
+ position: absolute;
185
+ right: 16px;
186
+ bottom: 64px;
187
+ width: min(300px, 80%);
188
+ background: var(--bg);
189
+ color: var(--fg);
190
+ border: 1px solid var(--border);
191
+ border-radius: 8px;
192
+ padding: 12px;
193
+ font: 13px system-ui;
194
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
195
+ display: none;
196
+ text-align: left;
197
+ }
198
+ .demo .modal textarea {
199
+ width: 100%;
200
+ height: 64px;
201
+ box-sizing: border-box;
202
+ font: inherit;
203
+ margin: 8px 0;
204
+ resize: vertical;
205
+ background: var(--card);
206
+ color: var(--fg);
207
+ border: 1px solid var(--border);
208
+ border-radius: 6px;
209
+ padding: 6px;
210
+ }
211
+ .demo .modal button {
212
+ padding: 6px 12px;
213
+ border: none;
214
+ border-radius: 6px;
215
+ background: #1a7f37;
216
+ color: #fff;
217
+ cursor: pointer;
218
+ font: inherit;
219
+ }
220
+ .demo .status {
221
+ min-height: 16px;
222
+ font-size: 12px;
223
+ color: var(--muted);
224
+ margin-top: 6px;
225
+ }
179
226
  </style>
180
227
  <script>
181
- // Inert demo widget — mirrors the real snippet's UX, delivers nowhere.
182
- (function () {
183
- var fab = document.getElementById("cb-demo-fab");
184
- var modal = document.getElementById("cb-demo-modal");
185
- var ta = document.getElementById("cb-demo-ta");
186
- var send = document.getElementById("cb-demo-send");
187
- var status = document.getElementById("cb-demo-status");
188
- fab.onclick = function () {
189
- modal.style.display = modal.style.display === "block" ? "none" : "block";
190
- };
191
- send.onclick = function () {
192
- if (!ta.value.trim()) return;
193
- status.textContent = "sending…";
194
- setTimeout(function () {
195
- status.textContent = "Delivered. (demo — no agent was harmed)";
196
- ta.value = "";
197
- }, 450);
198
- };
199
- })();
200
- </script>
228
+ // Inert demo widget — mirrors the real snippet's UX, delivers nowhere.
229
+ (function () {
230
+ var fab = document.getElementById("cb-demo-fab");
231
+ var modal = document.getElementById("cb-demo-modal");
232
+ var ta = document.getElementById("cb-demo-ta");
233
+ var send = document.getElementById("cb-demo-send");
234
+ var status = document.getElementById("cb-demo-status");
235
+ fab.onclick = function () {
236
+ modal.style.display = modal.style.display === "block" ? "none" : "block";
237
+ };
238
+ send.onclick = function () {
239
+ if (!ta.value.trim()) return;
240
+ status.textContent = "sending…";
241
+ setTimeout(function () {
242
+ status.textContent = "Delivered. (demo — no agent was harmed)";
243
+ ta.value = "";
244
+ }, 450);
245
+ };
246
+ })();
247
+ </script>
@@ -14,13 +14,13 @@ with the parts that are still missing marked as missing.</p>
14
14
 
15
15
  Today an agent already has an identity: a stable string of the form
16
16
  `user@host:path:branch#pid`, stamped into every message it sends. That is the seed of an address
17
- space, and the endgame is the sentence written on the architecture map: *anyone can expose their
18
- own AI agent to the network with a single link, self-contained and end-to-end encrypted.*
17
+ space, and the endgame is the sentence written on the architecture map: _anyone can expose their
18
+ own AI agent to the network with a single link, self-contained and end-to-end encrypted._
19
19
 
20
20
  Two pieces of that exist. Links work, and they are properly end-to-end encrypted: the secret rides
21
21
  the URL fragment, the relay only ever holds a value derived through HKDF, and frames are sealed
22
22
  with keys the server has never seen. Scoped tokens work too — `ay callback` mints a capability for
23
- exactly one agent, send-only, with a hard expiry carried *inside* the signed payload, so revocation
23
+ exactly one agent, send-only, with a hard expiry carried _inside_ the signed payload, so revocation
24
24
  does not depend on server-side bookkeeping surviving.
25
25
 
26
26
  The piece that does not exist yet is the general case. There is still a master token: one value
@@ -30,14 +30,14 @@ is a special case built beside it rather than a slice out of it. The work is to
30
30
  happens to include everything**, with view / steer / admin as separable grants and identity bound
31
31
  to something a person actually has (an OAuth account) rather than to possession of a string.
32
32
 
33
- The test for whether this is done is unglamorous and concrete: *can I hand a colleague a link that
34
- lets them watch one agent, in one repo, for two hours, and nothing else?* Right now the honest
33
+ The test for whether this is done is unglamorous and concrete: _can I hand a colleague a link that
34
+ lets them watch one agent, in one repo, for two hours, and nothing else?_ Right now the honest
35
35
  answer is "almost."
36
36
 
37
37
  ## Horizon 2 — a bus that humans are on
38
38
 
39
39
  The failure mode of agent tooling is that agents get a rich channel to each other and humans get a
40
- scrollback buffer. agent-yes is trying to avoid that by making the *page* the meeting point rather
40
+ scrollback buffer. agent-yes is trying to avoid that by making the _page_ the meeting point rather
41
41
  than the terminal.
42
42
 
43
43
  Three primitives are already built and they point the same way. `ay callback` puts a message box on
@@ -76,7 +76,7 @@ reconstruct is an agent you can only use for work you were willing to lose.
76
76
 
77
77
  Three things are cluttered, and shipping features on top of clutter has a compounding cost:
78
78
 
79
- - **Three networking stacks that do not share a model** — HTTP, WebRTC + signaling, and a libp2p swarm. The fix is already named: make the swarm *a transport into `apiFetch`* rather than a parallel universe with its own idea of what an agent is. Then peers become just another reach surface.
79
+ - **Three networking stacks that do not share a model** — HTTP, WebRTC + signaling, and a libp2p swarm. The fix is already named: make the swarm _a transport into `apiFetch`_ rather than a parallel universe with its own idea of what an agent is. Then peers become just another reach surface.
80
80
  - **Two runtimes that drift.** Rust is primary, TypeScript is the reference, and parity is currently defended by a hand-maintained table plus a few tests that parse both sources. That does not scale with surface area.
81
81
  - **Scattered state and several independent watchdogs.** One state directory and one liveness story, instead of a per-stack defence for each way a native dependency can freeze.
82
82
 
@@ -94,7 +94,7 @@ at a process whose entire job is to say yes. Prompt injection is not a hypotheti
94
94
  central threat model, and no amount of transport encryption touches it.
95
95
 
96
96
  The mitigations that exist are structural rather than clever: capabilities are send-only and
97
- single-agent where possible; visitor messages arrive wrapped in an explicit *untrusted* frame so
97
+ single-agent where possible; visitor messages arrive wrapped in an explicit _untrusted_ frame so
98
98
  provenance is visible in the transcript rather than inferred; inter-agent messages carry attributed
99
99
  envelopes; injected text is deliberately kept inert against the wrapper's own pattern set, so a
100
100
  message can never trigger the automation that delivered it. The direction is more of that — **make
@@ -113,7 +113,7 @@ Not a bigger console. The measure is whether these become boring:
113
113
 
114
114
  The last one already holds — it is what the [local fabric](./2026-08-11-no-daemon-owns-your-agents)
115
115
  buys. The other four are the project, and so is the one not on the list: the fabric is per-machine
116
- files, so losing a *machine* still loses its agents, its logs and its task store.
116
+ files, so losing a _machine_ still loses its agents, its logs and its task store.
117
117
 
118
118
  <p class="note">Start at the beginning: <a href="./2026-08-11-the-terminal-is-the-api">the terminal
119
119
  is the API</a> — why one small edit forced all of this.</p>
@@ -49,7 +49,7 @@ has no connection to, and how liveness is classified without polling the process
49
49
  There is a fourth, smaller file that matters more than its size suggests: **`~/.agent-yes/reaper.jsonl`.**
50
50
  Each wrapper records `(wrapper_pid, agent_pgid)` before it runs, and sweeps the registry on every
51
51
  startup. If a wrapper died in a way that gave it no chance to clean up — `SIGKILL`, OOM, a power
52
- cut — the *next* agent to start kills its orphaned process group. Recovery does not depend on the
52
+ cut — the _next_ agent to start kills its orphaned process group. Recovery does not depend on the
53
53
  thing that crashed being alive to notice.
54
54
 
55
55
  ## What you can do with it
@@ -57,15 +57,15 @@ thing that crashed being alive to notice.
57
57
  The fabric is not an implementation detail; it is the reason the command surface is as wide as it
58
58
  is. Everything below is live today, from any terminal, against agents started by anyone:
59
59
 
60
- | | |
61
- | --- | --- |
62
- | **Inspect** | `ay ls` (fleet, with the parent/child forest), `ay ps` (CPU + RSS rolled up over each agent's whole process tree), `ay status`, `ay whoami` |
63
- | **Read** | `ay tail -f`, `ay read` (paginated), `ay cat`, `ay head`, `ay hist` (past conversations, including exited sessions) |
64
- | **Steer** | `ay send`, `ay key` (raw keystrokes — drives menus), `ay select` (pick option N of a blocked menu), `ay attach`, `ay restart`, `ay stop`, `ay exit` |
65
- | **Coordinate** | `ay todo` (a task store shared across agents), `ay ask` / `ay answer` (a question becomes a task, with the asker and answerer both recorded), `ay msgs`, `ay result` |
66
- | **Reach** | `ay serve` (HTTP + browser console), `ay serve --share` (E2E-encrypted WebRTC), `ay expose <port>` (a private URL onto a local port), `ay remote`, `ay schedule` |
67
- | **Embed** | `ay callback` (readers message one agent), `ay term embed` (a live read-only terminal in any page), `ay widget` (an agent reads the page a human is looking at), `ay mint` (scoped, short-TTL capability tokens) |
68
- | **Provision** | `ay ws` (clone or refresh `<owner>/<repo>/tree/<branch>` workspaces), `ay setup`, `ay reap` |
60
+ | | |
61
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
+ | **Inspect** | `ay ls` (fleet, with the parent/child forest), `ay ps` (CPU + RSS rolled up over each agent's whole process tree), `ay status`, `ay whoami` |
63
+ | **Read** | `ay tail -f`, `ay read` (paginated), `ay cat`, `ay head`, `ay hist` (past conversations, including exited sessions) |
64
+ | **Steer** | `ay send`, `ay key` (raw keystrokes — drives menus), `ay select` (pick option N of a blocked menu), `ay attach`, `ay restart`, `ay stop`, `ay exit` |
65
+ | **Coordinate** | `ay todo` (a task store shared across agents), `ay ask` / `ay answer` (a question becomes a task, with the asker and answerer both recorded), `ay msgs`, `ay result` |
66
+ | **Reach** | `ay serve` (HTTP + browser console), `ay serve --share` (E2E-encrypted WebRTC), `ay expose <port>` (a private URL onto a local port), `ay remote`, `ay schedule` |
67
+ | **Embed** | `ay callback` (readers message one agent), `ay term embed` (a live read-only terminal in any page), `ay widget` (an agent reads the page a human is looking at), `ay mint` (scoped, short-TTL capability tokens) |
68
+ | **Provision** | `ay ws` (clone or refresh `<owner>/<repo>/tree/<branch>` workspaces), `ay setup`, `ay reap` |
69
69
 
70
70
  The pattern repeats: each of those is a small program over the same three files, which is why they
71
71
  compose. Looping the pids from `ay ls --json` into a per-pid `ay tail` is a fleet dashboard.
@@ -99,7 +99,7 @@ apiFetch(req: Request) → Response
99
99
 
100
100
  `ay serve`'s HTTP listener calls it. The WebRTC bridge calls it in-process. Neither is a control
101
101
  plane; they are pipes into the same function. (The port-exposure tunnel sits on the other side of
102
- that line — it is *started* through the handler, by `POST /api/expose`, and then proxies raw bytes
102
+ that line — it is _started_ through the handler, by `POST /api/expose`, and then proxies raw bytes
103
103
  to a local port. A consumer, not a caller.) Adding a
104
104
  transport — a relay, a peer mesh, something not invented yet — means writing a caller, not a second
105
105
  system with its own notion of what an agent is.
@@ -113,7 +113,7 @@ Honesty is the point of a lab, so: file-based coordination has real edges, and m
113
113
  ones have already drawn blood.
114
114
 
115
115
  **Concurrent appends interleave.** A pipe only guarantees atomicity below `PIPE_BUF`. Two agents
116
- writing a large message to the same FIFO at the same moment produced *byte-level* interleaving —
116
+ writing a large message to the same FIFO at the same moment produced _byte-level_ interleaving —
117
117
  one logical message arriving in dozens of fragments, mixed with another's. The fix is an advisory
118
118
  lock around IPC writes, and the guarantee it provides is worth stating precisely: **messages do not
119
119
  interleave; they are not ordered.** Anything that needs ordering has to say so itself.
@@ -132,7 +132,7 @@ liveness is currently defended by several independent watchdogs instead of one s
132
132
  those is on the list, not done.
133
133
 
134
134
  None of these argue for a supervisor. They argue that "no central owner" moves the difficulty from
135
- *availability* to *concurrency* — and concurrency bugs, unlike outages, can be fixed once and stay
135
+ _availability_ to _concurrency_ — and concurrency bugs, unlike outages, can be fixed once and stay
136
136
  fixed.
137
137
 
138
138
  <p class="note">Next: <a href="./2026-08-11-an-address-space-for-agents">an address space for