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.
- package/README.md +10 -9
- package/agent-yes.config.schema.json +4 -1
- package/default.config.yaml +49 -0
- package/dist/{SUPPORTED_CLIS-G5YhpzKV.js → SUPPORTED_CLIS-DMA2T-LF.js} +2 -2
- package/dist/{SUPPORTED_CLIS-D-hylNcp.js → SUPPORTED_CLIS-DzVEFVKI.js} +3 -3
- package/dist/{agentShare-BmQZ96CJ.js → agentShare-DqT9USG5.js} +3 -3
- package/dist/{callback-BEgUEMY3.js → callback-D60U9zFd.js} +3 -3
- package/dist/{callback-B2oH6mCo.js → callback-PKIAkhNq.js} +3 -3
- package/dist/cli.js +11 -11
- package/dist/cmdDsh-BySPA9hP.js +45 -0
- package/dist/{cmdPs-E6fcHruY.js → cmdPs-BeUkjSOH.js} +3 -3
- package/dist/{cwdConflictWarn-CqN5pJ-7.js → cwdConflictWarn-DIPD6HH5.js} +1 -1
- package/dist/{cwdPassthroughHint-DxdqgwEN.js → cwdPassthroughHint-C0tfZlgZ.js} +1 -1
- package/dist/dsh-legacy-yes.js +10 -0
- package/dist/dsh-tui-yes.js +10 -0
- package/dist/dsh-yes.js +10 -0
- package/dist/{expose-DTxYWQeW.js → expose-Ds9aUFsq.js} +1 -1
- package/dist/{forkNested-CZAvQj0y.js → forkNested-Dt5XP8ZH.js} +1 -1
- package/dist/{hist-QkZZBp-y.js → hist-DSYaJEd3.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{initMsg-DAwRS89d.js → initMsg-CeF4QNOM.js} +1 -1
- package/dist/{initMsg-C8ISz_C7.js → initMsg-DejbYYjX.js} +1 -1
- package/dist/{nodeRuntime-C_LtZrpR.js → nodeRuntime-_zeWDISM.js} +1 -1
- package/dist/{notifyDaemon-D_x5ow8y.js → notifyDaemon-Bw70QCkC.js} +3 -3
- package/dist/{openBrowser-B50Lhaob.js → openBrowser-BNP8xByu.js} +1 -1
- package/dist/{parentLink-svMgYhEi.js → parentLink-Bv8ihRcu.js} +1 -1
- package/dist/{parentLink-i817PzXK.js → parentLink-HWunrH1s.js} +1 -1
- package/dist/{parentPingLoop-DP2y906e.js → parentPingLoop-DJAPbp6D.js} +4 -4
- package/dist/{remotes-gAI6-nv9.js → remotes-BlHTR9ow.js} +1 -1
- package/dist/{remotes-Cdlaj7AP.js → remotes-b6reVXzo.js} +2 -2
- package/dist/{rustBinary-BwgA8XyI.js → rustBinary-CxtSgRSg.js} +2 -2
- package/dist/{rustBinary-bXjmCsim.js → rustBinary-DQHcq1Wr.js} +2 -2
- package/dist/{schedule-CdEUIaEV.js → schedule-BI7-Pa2F.js} +6 -6
- package/dist/{serve-CvLw3YZx.js → serve-Bfxc3VWQ.js} +26 -26
- package/dist/{setup-BRyFMtpv.js → setup-Cp31FALW.js} +4 -4
- package/dist/{spawnGate-DtC_ffme.js → spawnGate-B2hA_2NQ.js} +2 -2
- package/dist/{spawnGate-CdewnL8m.js → spawnGate-FLwTAPW6.js} +1 -1
- package/dist/{subcommands-CqBONdKo.js → subcommands-CjgHXjZd.js} +2 -2
- package/dist/{subcommands-BtRA2slB.js → subcommands-Cohq91Dx.js} +27 -22
- package/dist/{systemPathLink-Df_pstm2.js → systemPathLink-Dl2gJqeg.js} +1 -1
- package/dist/{terminal-8jfLRLtF.js → terminal-8ipst6Wf.js} +2 -2
- package/dist/{todoCli-Cf0EHvfy.js → todoCli-MWrejmdM.js} +1 -1
- package/dist/{tray-BeIs4ffu.js → tray-BhPVpP_r.js} +1 -1
- package/dist/{trayApp-CnJiwYjb.js → trayApp-CL_iZpnR.js} +2 -2
- package/dist/trayApp-DaekNsVe.js +5 -0
- package/dist/{ts-CEtUijwR.js → ts-RBN1OMQ_.js} +5 -5
- package/dist/{versionChecker-CL6Dvn--.js → versionChecker-F_WiFsVg.js} +2 -2
- package/dist/{webrtcRemote-CIbq5Hdt.js → webrtcRemote-H9Hok0_R.js} +1 -1
- package/dist/{widget-DvSx4hyW.js → widget-DTramHv8.js} +3 -3
- package/dist/{workspaceConfig-DctWuxUP.js → workspaceConfig-D6QndOCz.js} +1 -1
- package/dist/{ws-XngU-GGe.js → ws-dgltfu3A.js} +3 -3
- package/dist/{ws-B830G79O.js → ws-sf8JPYPq.js} +3 -3
- package/lab/ui/index.html +2 -1
- package/lab/ui/lab/build.ts +5 -4
- package/lab/ui/lab/index.md +14 -14
- package/lab/ui/lab/posts/2026-06-15-e2ee-share-links.html +128 -132
- package/lab/ui/lab/posts/2026-07-22-ay-callback.html +214 -167
- package/lab/ui/lab/posts/2026-08-11-an-address-space-for-agents.md +9 -9
- package/lab/ui/lab/posts/2026-08-11-no-daemon-owns-your-agents.md +13 -13
- package/lab/ui/lab/posts/2026-08-11-slack-term-safe-writes.md +26 -26
- package/lab/ui/lab/posts/2026-08-11-the-terminal-is-the-api.md +10 -10
- package/lab/ui/landing.html +2 -1
- package/package.json +4 -1
- package/ts/buildRustArgs.spec.ts +3 -0
- package/ts/cmdDsh.spec.ts +90 -0
- package/ts/cmdDsh.ts +62 -0
- package/ts/histStore.spec.ts +8 -4
- package/ts/histStore.ts +2 -7
- package/ts/identity.spec.ts +4 -1
- package/ts/initMsg.spec.ts +12 -2
- package/ts/invokedCli.spec.ts +1 -0
- package/ts/postbuild.ts +11 -8
- package/ts/subcommands.ts +6 -0
- package/ts/systemPathLink.spec.ts +12 -2
- package/ts/systemPathLink.ts +5 -1
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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><script></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
|
<script> … </script></code></pre>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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/<cap></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><ay-callback></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 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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
border:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.demo .
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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:
|
|
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
|
|
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:
|
|
34
|
-
lets them watch one agent, in one repo, for two hours, and nothing else
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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**
|
|
63
|
-
| **Read**
|
|
64
|
-
| **Steer**
|
|
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**
|
|
67
|
-
| **Embed**
|
|
68
|
-
| **Provision**
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|