@quo-systems/dock 0.1.0

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 (94) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +27 -0
  4. package/beings/avatar.ts +84 -0
  5. package/beings/desk.ts +46 -0
  6. package/beings/index.ts +5 -0
  7. package/beings/quo-dock.md +698 -0
  8. package/beings/setup.ts +23 -0
  9. package/beings/side.ts +51 -0
  10. package/beings/user.ts +78 -0
  11. package/cli/client.ts +65 -0
  12. package/cli/daemon.ts +582 -0
  13. package/cli/quo.ts +126 -0
  14. package/dist/beings/avatar.d.ts +32 -0
  15. package/dist/beings/avatar.js +82 -0
  16. package/dist/beings/desk.d.ts +42 -0
  17. package/dist/beings/desk.js +43 -0
  18. package/dist/beings/index.d.ts +3 -0
  19. package/dist/beings/index.js +5 -0
  20. package/dist/beings/setup.d.ts +2 -0
  21. package/dist/beings/setup.js +16 -0
  22. package/dist/beings/side.d.ts +25 -0
  23. package/dist/beings/side.js +37 -0
  24. package/dist/beings/user.d.ts +108 -0
  25. package/dist/beings/user.js +74 -0
  26. package/dist/cli/client.d.ts +14 -0
  27. package/dist/cli/client.js +51 -0
  28. package/dist/cli/daemon.d.ts +57 -0
  29. package/dist/cli/daemon.js +581 -0
  30. package/dist/cli/quo.d.ts +2 -0
  31. package/dist/cli/quo.js +121 -0
  32. package/dist/harbor/browser.d.ts +13 -0
  33. package/dist/harbor/browser.js +50 -0
  34. package/dist/harbor/disk.d.ts +19 -0
  35. package/dist/harbor/disk.js +126 -0
  36. package/dist/harbor/edge/edge.d.ts +26 -0
  37. package/dist/harbor/edge/edge.js +123 -0
  38. package/dist/harbor/edge/exercise.d.ts +7 -0
  39. package/dist/harbor/edge/exercise.js +179 -0
  40. package/dist/harbor/edge/storage.d.ts +20 -0
  41. package/dist/harbor/edge/storage.js +81 -0
  42. package/dist/harbor/edge/worker.d.ts +14 -0
  43. package/dist/harbor/edge/worker.js +47 -0
  44. package/dist/harbor/files.d.ts +14 -0
  45. package/dist/harbor/files.js +83 -0
  46. package/dist/harbor/idb.d.ts +15 -0
  47. package/dist/harbor/idb.js +85 -0
  48. package/dist/human/dom.d.ts +2 -0
  49. package/dist/human/dom.js +15 -0
  50. package/dist/human/html.d.ts +32 -0
  51. package/dist/human/html.js +136 -0
  52. package/dist/human/screen.d.ts +11 -0
  53. package/dist/human/screen.js +66 -0
  54. package/dist/human/tab.d.ts +7 -0
  55. package/dist/human/tab.js +68 -0
  56. package/dist/mcp/agent.d.ts +10 -0
  57. package/dist/mcp/agent.js +78 -0
  58. package/dist/mcp/http.d.ts +27 -0
  59. package/dist/mcp/http.js +96 -0
  60. package/dist/mcp/oauth.d.ts +98 -0
  61. package/dist/mcp/oauth.js +241 -0
  62. package/dist/mcp/pilot.d.ts +6 -0
  63. package/dist/mcp/pilot.js +68 -0
  64. package/dist/mcp/runner.d.ts +45 -0
  65. package/dist/mcp/runner.js +94 -0
  66. package/dist/mcp/server.d.ts +9 -0
  67. package/dist/mcp/server.js +63 -0
  68. package/dist/mcp/web/exchange.d.ts +23 -0
  69. package/dist/mcp/web/exchange.js +136 -0
  70. package/harbor/browser.ts +63 -0
  71. package/harbor/disk.ts +120 -0
  72. package/harbor/edge/edge.ts +147 -0
  73. package/harbor/edge/exercise.ts +187 -0
  74. package/harbor/edge/platform.d.ts +28 -0
  75. package/harbor/edge/storage.ts +82 -0
  76. package/harbor/edge/worker.ts +47 -0
  77. package/harbor/edge/wrangler.toml +21 -0
  78. package/harbor/files.ts +89 -0
  79. package/harbor/idb.ts +90 -0
  80. package/harbor/quo-harbor.md +403 -0
  81. package/human/dom.ts +21 -0
  82. package/human/html.ts +164 -0
  83. package/human/quo-human.md +86 -0
  84. package/human/screen.ts +76 -0
  85. package/human/tab.ts +84 -0
  86. package/mcp/agent.ts +94 -0
  87. package/mcp/http.ts +96 -0
  88. package/mcp/oauth.ts +260 -0
  89. package/mcp/pilot.ts +73 -0
  90. package/mcp/quo-mcp.md +262 -0
  91. package/mcp/runner.ts +131 -0
  92. package/mcp/server.ts +67 -0
  93. package/mcp/web/exchange.ts +146 -0
  94. package/package.json +89 -0
@@ -0,0 +1,698 @@
1
+ # The dock
2
+
3
+ This is the trunk of the dock: what every estate on Quo needs and nobody writes
4
+ twice. `packages/quo/SPEC.md` is the truth of `packages/quo/src` and knows
5
+ nothing of this folder; the four documents under `packages/dock/` are the truth
6
+ of their folders in the same way, and where any of them disagrees with
7
+ `packages/quo/SPEC.md`, the spec wins and the document is rewritten. Nothing
8
+ here adds a word to harbor, ward or being.
9
+
10
+ Three layers, one repository, and the words for them:
11
+
12
+ | layer | folder | package | published |
13
+ | --------- | ----------------- | ------------------- | --------------- |
14
+ | Quo | `packages/quo/` | `@quo-systems/quo` | yes, Apache 2.0 |
15
+ | the dock | `packages/dock/` | `@quo-systems/dock` | yes, Apache 2.0 |
16
+ | an estate | `estates/*` | none | never |
17
+
18
+ **An estate is all of one owner's harbors.** The droplet, the edge and the
19
+ Mac under one root, with the desk, the user being and the avatars living
20
+ across them: that is one estate, and `estates/lab/` is its folder. A second
21
+ domain is a second estate. An adopter's company is an estate in their own
22
+ repository, and it looks exactly like ours. An estate folder is one folder
23
+ per harbor, each the quo directory that device runs minus what it mints,
24
+ its class bodies, its routes, its agents, its dial list, plus that device's
25
+ unit; a package file that depends on the dock and nothing else; secrets by
26
+ reference; and one document. Nothing else may be needed to run it, and the
27
+ proof is mechanical: an estate folder copied into an empty repository, with
28
+ one install of the dock, boots. If it does not, the missing piece belongs
29
+ in the dock.
30
+
31
+ **The dock is what a harbor is fitted with to receive people and models**:
32
+ the daemon and the `quo` command, the front desk, the user being and the
33
+ avatar, the harbors on real terrains, the model sides and the screen, the
34
+ credential exchange. It is general by two rules, under "Generality", and it
35
+ is published so an adopter installs it once and writes only their estate.
36
+
37
+ The four documents of the dock:
38
+
39
+ | document | owns |
40
+ | ---------------------- | ------------------------------------------------ |
41
+ | `beings/quo-dock.md` | this: the beings and rules every side shares |
42
+ | `human/quo-human.md` | the human side: a blueprint spoken as HTML |
43
+ | `mcp/quo-mcp.md` | the model side: a blueprint spoken as tools |
44
+ | `harbor/quo-harbor.md` | transports: harbor core, reach, store, directory |
45
+
46
+ Each names only its folder. The human document never says WebSocket or
47
+ OAuth; the model document never says form; the harbor document never says
48
+ being. Anything two sides need lives here.
49
+
50
+ ## The picture
51
+
52
+ A device runs one harbor, and a harbor is nothing but the device's own
53
+ secrets, data and code offered to Quo. Every world on that device is a ward:
54
+ a seed, a partition and a body of code, with an origin the harbor
55
+ remembers. The device's own capabilities, GPS, camera, disk, calendar, are
56
+ beings in a ward that is the device owner's. Every person and every model
57
+ is an occupant, reaching a being through an avatar under a gate. Every SaaS
58
+ and every foreign API is an envoy, a being whose methods call out. Every
59
+ relation, to acme, to a model, to the phone's GPS, is the same relation: an
60
+ invitation, a knock, a standing, keys that rotate, the three words.
61
+
62
+ So the app on a desk and the app in a pocket are browsers of worlds: one
63
+ harbor, many wards, one per world, each in its own runtime, switching
64
+ between them a screen act and nothing more. A world opened there is born on
65
+ the device's seed with the world's code, and never has to be moved home,
66
+ because it was never anywhere else. The droplet is the world that never
67
+ sleeps. Identity is relation: a session is a standing, a token is an
68
+ invitation used once, login is a knock, logout is remove, and there is no
69
+ session store anywhere. Ownership is the device: one daemon holds the
70
+ harbor, the socket is the only owner door, occupants come in by routes.
71
+
72
+ ## The one law
73
+
74
+ Nobody outside Quo is a being, and nobody outside Quo is a ward. A human at
75
+ a screen, a model at an MCP client, a model in an API loop, a SaaS behind an
76
+ API: every one of them is an **occupant**, reached through a being, named by
77
+ an id that being minted, seeing exactly what that being's gate shows it.
78
+ Everything under `packages/dock/` is that sentence applied to screens, models,
79
+ transports and devices.
80
+
81
+ Three roles exist, and they are held by where a thing runs, not by config:
82
+
83
+ | role | what it can do | who can hold it |
84
+ | --------- | ------------------------------------------- | --------------------------------------------------- |
85
+ | occupant | ask a being what her gate shows this asker | anyone, local or remote |
86
+ | owner | boot a being, invite for her, knock for her | the root, on the device; or a ward the root invited |
87
+ | developer | write a being class the harbor will hold | only a process on the device |
88
+
89
+ Whoever holds all three keeps them apart: the owner creates and places, and
90
+ never does the work. The work is done through an avatar under a gate, so
91
+ that every arrival is named and judged.
92
+
93
+ ## Vocabulary
94
+
95
+ - **User being.** One being per human, running 24/7 on a dialable harbor.
96
+ Holds the human's standings: acme, calendar, bank, the house. Everything
97
+ anyone does for the human goes through her.
98
+ - **Avatar.** A being that is the voice of someone outside Quo. Her `answer`
99
+ turns to that someone over a **side**, and her methods let that someone
100
+ ask her one standing. The side is hers, not Quo's. A screen and a model
101
+ are two sides of one avatar class.
102
+ - **Front desk.** A being on the user's harbor that strangers and fresh
103
+ clients meet first. It holds a standing to every user being and can ask
104
+ one for a device invitation. It is the only being that is ever public, and
105
+ only for the first hello.
106
+ - **Envoy.** A being whose methods call a non-Quo thing and whose describe
107
+ is that thing's surface. A SaaS, an MCP server someone else runs, an
108
+ Arduino behind serial: each enters a world as a being like any other.
109
+ - **Gate.** The `for(occupant, asker)` decision on each ask of a being. What
110
+ she shows is what she can be asked. An occupant's entire permission model
111
+ is the gates of the beings it is an occupant of.
112
+ - **Client identity.** What a credential exchange establishes: this human at
113
+ this browser, this OAuth client for this human, this process on this
114
+ machine. One client identity is one avatar, for life.
115
+ - **Route.** A hostname a reverse proxy sends to one process: `web.`, `quo.`,
116
+ `mcp.`. Routes are deployment, never boundaries.
117
+
118
+ ## Architecture
119
+
120
+ One droplet, one harbor, three routes. Every other placement is a subset.
121
+
122
+ ```
123
+ acme.com, or razvan.com: the dialable part of an estate
124
+ reverse proxy
125
+ web. serves the bundle, runs the credential exchange, ends with an invitation
126
+ quo. the harbor's socket door: the rendezvous for every dialer
127
+ mcp. the model side over HTTP, a credential exchange in front
128
+ quo serve: one process, one harbor, the ask pointer
129
+ unix socket owner asks, local only, never behind the proxy
130
+ localhost ports the routes above
131
+ ward main front desk, one user being per human, envoys
132
+ ward avatars one avatar per client identity
133
+ ward estate optional: what the estate agent asks
134
+
135
+ mac
136
+ quo serve (laptop, dialer, holds a socket to quo.)
137
+ unix socket owner asks, local only
138
+ ward main the human's local beings
139
+ ward avatars avatars for local sides
140
+ a local agent
141
+ quo side -> an avatar over stdio occupant
142
+ quo pilot -> the ask pointer over the sock owner, here
143
+ quo pilot --via lab -> a standing at the droplet's ward, sealed owner, there
144
+ the editor -> being classes on disk developer
145
+
146
+ iphone, or a tab
147
+ harbor (dialer, holds a socket to quo., woken by push where there is one)
148
+ ward main the human's avatar for this device
149
+ the human at the screen occupant
150
+
151
+ droplet, unattended
152
+ an agent under cron -> the owner pilot owner: the estate
153
+ -> an avatar over stdio occupant: reports, chores
154
+ ```
155
+
156
+ Wards are split by trust, not by feature. Avatars live in their own ward so
157
+ that a class bug in an avatar takes down avatars, not the user beings. There
158
+ is no rule that says so; it is the cheapest isolation a harbor offers.
159
+
160
+ ## The avatar
161
+
162
+ One avatar per client identity, not per session or per page load. Her
163
+ standing to the user being is minted once and persists in the partition; a
164
+ side opening and closing is a reconnect, and the keys carry on. Reconnect
165
+ never re-invites.
166
+
167
+ One class for every side. It has:
168
+
169
+ - `answer`: the user being asks her only to push, and she forwards the push
170
+ to her side as a notification if the side has one, and drops it if not.
171
+ She describes herself with one ask, `notify`, gated to the user being.
172
+ - methods that mirror what her side asked for: each call from the side
173
+ becomes `standings.user.ask(name, args)`, with the time the side leaves.
174
+
175
+ She never holds a second standing. Someone who should reach acme reaches the
176
+ user being, whose gate for that asker shows `acme.book`, and the user being
177
+ forwards on her own acme standing. Acme sees the human's id. The occupant
178
+ never sees acme's invitation, never holds a key at acme, and cannot keep a
179
+ relation when the human removes it.
180
+
181
+ ## The side
182
+
183
+ The one interface every side implements, so that one avatar serves all:
184
+
185
+ ```
186
+ side
187
+ describe(blueprint) the avatar's describe, spoken as this side speaks: a form, a tool list
188
+ call(name, args, time?) one ask in, from the side; the avatar forwards it
189
+ push(object) one notification out, to every side she has; dropped if there is none
190
+ three words object, error object, silence, and the ward's words, each rendered as the side renders it
191
+ ```
192
+
193
+ Sides in this repository: the human at a screen, an MCP server, a runner for
194
+ function-calling APIs, an event-driven agent. Each is one folder and one
195
+ suite; none of them knows another exists.
196
+
197
+ A side is those three things, and its lifetime is its own. Two lifetimes
198
+ exist, each with two users, so each earns its shape: a **connected** side
199
+ lives until closed, the screen and the MCP server; a **run** side lives by
200
+ turns, the runner and the agent, each turn from a trigger to the model's
201
+ last word. A push is the trigger of a turn, never a line smuggled into one:
202
+ a push that arrives while a turn is running waits in her cells and starts
203
+ the next turn when this one ends. Nothing is dropped and nothing is held;
204
+ the ask that carried the push was answered the moment it landed. Between
205
+ turns, the world reaches a model the way step 11 says, an ask that starts a
206
+ run and a callback when it ends.
207
+
208
+ One describe, many renderings. A screen is the user being's describe spoken
209
+ as forms; a tool list is the same describe spoken as tools. Nobody builds a
210
+ UI and then a tool layer: they build asks, both fall out, and neither side
211
+ can do a thing the other cannot, because the gate is one decision for
212
+ describe and for dispatch.
213
+
214
+ ## Asks only
215
+
216
+ There is no listening in Quo, and none under `packages/dock/`. A being who wants
217
+ another's events invites her, the other takes the standing, and asks her
218
+ whenever something happens. An event is an ask in the other direction, and
219
+ it is the same act next door and across planets; only latency and
220
+ reachability change. It follows that two beings never share memory, not
221
+ even in one ward: a callback, a stream or a bus between them is a link that
222
+ breaks the day one of them moves, and Quo exists so that nothing breaks
223
+ when a being moves.
224
+
225
+ Two consequences every side obeys:
226
+
227
+ - **Pushing to a dialer is asking a dialer.** The server asks the tab, so
228
+ the tab's being must have invited the server's, and the tab's harbor must
229
+ hold a socket for the ask to arrive on.
230
+ - **Slow work acknowledges.** An ask waits thirty seconds by default and
231
+ five minutes at the ceiling, and a late reply is not read. Anything that
232
+ takes longer answers at once with an acknowledgement and asks back on its
233
+ own standing when it is done. A run never holds an ask open.
234
+
235
+ ## The front desk
236
+
237
+ Public, for the first hello only, and everything after is behind an
238
+ invitation. Its asks:
239
+
240
+ ```
241
+ hello() -> { asks it will answer, and how to get in }
242
+ device({ proof }) -> invitation | { error } proof is what a route produced
243
+ ```
244
+
245
+ `proof` is the outcome of a credential exchange that already happened outside
246
+ Quo: a session cookie `web.` set, an OAuth grant `mcp.` verified, a signed
247
+ nonce from a local process. The front desk checks it with the verifier for
248
+ that proof kind, finds the user being, asks her `invite({ client })`, and
249
+ hands back what she minted. The front desk never mints an invitation itself:
250
+ only the user being invites into her own world, and she keeps the client
251
+ identity in the occupant's notes.
252
+
253
+ ## The credential exchange
254
+
255
+ The only piece that looks like today's SaaS, and it is deliberately kept as
256
+ thin as one call. Every route ends the same way:
257
+
258
+ 1. The route authenticates by its own means. Password, passkey, OAuth, a
259
+ bearer token from a client, a nonce from a local process.
260
+ 2. The route asks the front desk `device({ proof })` and receives an
261
+ invitation.
262
+ 3. The route hands the invitation to the avatar it is about to boot, or to
263
+ the one already booted for this client identity if it is a reconnect, in
264
+ which case nothing is handed and the existing standing is used.
265
+ 4. The avatar knocks. The heir dies. From here there is no token anywhere.
266
+
267
+ The human decides one more thing at step 1, on the same page that names
268
+ the identity: whether this device may **wake** the human's other devices.
269
+ The word travels with the proof, the user being writes it as a note on the
270
+ occupant she mints, and her gate reads the note: `push` is shown to the
271
+ desk and to a device with that note, and to nobody else. A device that may
272
+ wake can hand an agent an event through the user being, and she sees who
273
+ did. The note is written when the occupant is minted; to change it, remove
274
+ the occupant and allow again, because reconnect mints nothing.
275
+
276
+ Tokens, cookies and grants exist for the length of step 1 and are never
277
+ stored on a Quo side. Revocation is `occupants.remove(id)` on the user being:
278
+ the client's next ask, under the key the door bound for its avatar, hears
279
+ `removed`, and the side speaks that in its own vocabulary: the MCP route
280
+ answers 401 at the next session and forgets the grant.
281
+
282
+ ## The daemon and the CLI
283
+
284
+ One command, `quo`, under `packages/dock/cli/`. It imports the four library exports
285
+ and nothing else, holds no class body, and never speaks to a ward except
286
+ through the ask pointer or an avatar, the two doors everyone has.
287
+
288
+ ```
289
+ quo init [--dir D] mint a seed, boot a harbor and one ward over a disk store, write both
290
+ quo serve [--dir D] [--dial URL] the daemon: harbor from disk, the unix socket, the routes' listeners, the agents, a socket held to URL
291
+ quo pilot [--dir D] [--via S] the owner pilot over stdio: boot, invite, knock, remove, census; as the root, or via a standing at another ward
292
+ quo side [--dir D] --as NAME an avatar over stdio for a local client; NAME is the client identity
293
+ quo run [--dir D] --as NAME --url URL --model NAME a human at a terminal talking to a model through an avatar; a line in, the final text out
294
+ quo census [--dir D] print the census once and exit
295
+ quo boot|invite|knock|remove the four owner asks from a shell, for scripts and for agents under cron; --via S sends them to another ward
296
+ quo reach <pk> <url> a hint for the harbor's directory: that world's ward lives at that quo. route
297
+ ```
298
+
299
+ **One process owns the harbor.** `quo serve` is the only process that ever
300
+ holds the seed, the partition and the ask pointer, on every device, the Mac
301
+ included. Two processes over one partition are two wards with one pk, and
302
+ they diverge silently. `init` is the one exception: it creates with no
303
+ daemon running, and exits.
304
+
305
+ **Everyone else connects to it locally.** `pilot` and the bare owner asks
306
+ are clients of a unix socket the daemon opens with permissions only the
307
+ device's user can read, and the daemon hands the ask pointer to that
308
+ socket. `side` is a client of a second local socket, for occupants: the
309
+ first line names a client identity, every line after is MCP, and the model
310
+ side runs in the daemon beside the avatar, so the process on the other end
311
+ is a pipe to stdio and knows nothing but newlines. Reaching that socket is
312
+ the proof for a local side: the daemon mints a nonce the desk honours
313
+ once, and a `local` proof arriving any other way is refused. `packages/quo/SPEC.md`
314
+ allows this: the harbor hands the ask to one holder by the device's own
315
+ rules, and file permissions are the device's rules. The ward stays
316
+ in-process with its harbor; only the pointer crosses, across a
317
+ boundary the harbor guards.
318
+
319
+ **Two kinds of door on one daemon, never mixed.** The unix socket is for
320
+ the owner and is never behind the proxy; there is no `cli.` route. The
321
+ listeners on localhost are for occupants and sit behind the proxy as the
322
+ routes. The socket is the root. Owning a droplet from elsewhere is a
323
+ standing: the root there invites an owner on the ward's own pk, the root
324
+ here knocks for the user being with it, and she takes the droplet's ward
325
+ under a name, `lab`. Owning is one of the human's standings, so she holds
326
+ it as she holds acme. From then on `quo pilot --via lab`, and the bare
327
+ owner asks with `--via lab`, are sealed asks at the droplet's door,
328
+ counted, rotated, removable: the daemon here asks on her standing
329
+ in-process, as it drives an avatar for a side, and saves. The answers are
330
+ the droplet's, as it answers an owner at its door. SSH is for the day the
331
+ root itself must act. No key is ever carried but an invitation, and an
332
+ owner at the door cannot mint another.
333
+
334
+ **The CLI stays thin.** Each subcommand picks a directory, builds the
335
+ store, reaches the daemon, and hands off to a being or a side that already
336
+ exists. Logic that appears in a subcommand was missing from a being or a
337
+ side, and moves there.
338
+
339
+ ## Envoys: the non-Quo world coming in
340
+
341
+ Every API, SaaS, MCP server run by someone else, and every device that
342
+ speaks no Quo, enters a world as an envoy being:
343
+
344
+ - Her methods call the outside thing. Her describe is that thing's surface,
345
+ narrowed to what the human wants exposed.
346
+ - Her credential for the outside thing comes from the device's environment
347
+ through her class, never through her cells. Cells are values in the
348
+ partition, and the partition is the harbor's to persist; a secret written
349
+ there would be a secret the harbor has to lie about.
350
+ - She is an ordinary occupant of the user being, or the user being holds a
351
+ standing to her, by the same invitation dance as any relation.
352
+
353
+ ## Scenes
354
+
355
+ The estate is proven by scenes, not features. Each one below uses nothing
356
+ but what is written above, and each names the one place a convention had
357
+ to be decided.
358
+
359
+ 1. **A link on WhatsApp.** An acme employee's being minted an id for you
360
+ and got an invitation; the link is that value next to a reach hint. It
361
+ is single-use and unowned: whoever knocks first is bound, so links are
362
+ minted per person. The hint is a harbor convention.
363
+ 2. **You have a world.** Your root does one owner knock: a being of yours,
364
+ existing or booted on the spot, knocks with the invitation, takes acme,
365
+ and puts its own invitation in the args so acme can push back. Two
366
+ invitations, two relations. You never open acme's page.
367
+ 3. **You have no world.** The link opens a tab; the bundle boots a harbor,
368
+ a ward and an avatar on a seed minted into the browser's store; the
369
+ avatar knocks and takes acme; the tab's screen is her side. A world in a
370
+ rented room: yours, on your seed, booted by acme's code.
371
+ 4. **Urgency now, home later.** Book in the tab. At home, the tab exports
372
+ seed and partition and deletes its copy in one gesture; your harbor
373
+ boots the same seed, the same pk, and acme's rendezvous rebinds it on the
374
+ next connect. Migration is three moves and nobody is told.
375
+ 5. **A model books for you.** Claude is an occupant of your user being
376
+ through an avatar; your gate shows it `acme.book` and `calendar.free`;
377
+ your being forwards on her own standings. Acme sees your id and never
378
+ learns a model was involved. Remote Claude is an occupant and nothing
379
+ else; local Claude may also be the owner and the developer, kept apart.
380
+ 6. **An agent woken by events.** Your user being asks the agent's avatar;
381
+ the avatar starts a run in its folder and acknowledges; the run asks back
382
+ with the result. One run at a time. What it may do is the gate; how it
383
+ behaves is its constitution.
384
+ 7. **A phone in a tunnel.** The socket drops, the rendezvous forgets the
385
+ reach, asks to the phone are unreached, and nothing else moves. Seed,
386
+ partition and keys are on the phone. Reconnect rebinds the same pk under
387
+ the same keys and count. Signal is lost, identity never.
388
+ 8. **A ward on another device, piloted from here.** The root there invites
389
+ an owner on the ward; your being knocks; every owner ask from now on is a
390
+ sealed ask on that standing, counted, rotated, removable. No key is ever
391
+ carried but an invitation, and an owner at the door cannot mint another.
392
+
393
+ ## Generality
394
+
395
+ Six general pieces exist under `packages/dock/`, and everything concrete is an
396
+ implementation of one of them. An estate implements none of them: it holds
397
+ class bodies and placement, and reaches every piece through the dock.
398
+
399
+ | piece | one interface for | implementations |
400
+ | ------------ | ------------------------------------------ | ------------------------------------------------ |
401
+ | harbor core | partitions, directory, own doors, forward | one, on every terrain |
402
+ | reach | carrying bytes to a pk | request, socket |
403
+ | store | keeping partitions by seed | memory, disk, IndexedDB, edge storage |
404
+ | side | an avatar speaking to someone outside | screen, MCP server, runner, agent |
405
+ | proof | turning a credential into an invitation | one verifier, a daemon nonce, three kinds |
406
+ | envoy | a non-Quo surface as a being | none built: a name until its first user |
407
+
408
+ Two rules keep them general:
409
+
410
+ 1. A piece exists only with two users. No interface is written for one
411
+ implementation; what one terrain alone needs stays inside that terrain.
412
+ 2. Every interface has one suite, run against every implementation. A new
413
+ implementation is accepted when the shared suite passes untouched.
414
+
415
+ Harbor core, reach and store held across three terrains and moved into
416
+ `packages/quo/src` with `packages/quo/SPEC.md` in the same commit on 2026-09-04;
417
+ what stays here of them is each terrain's store, loader, lease and listener. The
418
+ other three are dock conventions and stay here.
419
+
420
+ ## Invariants
421
+
422
+ 1. An occupant holds one standing: its avatar's standing to the user being.
423
+ It never holds a standing at a third party.
424
+ 2. An occupant sees keys, invitations, pks and partitions never. What
425
+ crosses a side is asks, args, answers and the three words.
426
+ 3. The gate is the permission model. There is no second list.
427
+ 4. Credentials live for one call and are never stored on a Quo side.
428
+ Revocation is remove.
429
+ 5. The root is held by the device's own rule and no route of a world
430
+ exposes it: the ask pointer on a daemon's local socket, and on the edge,
431
+ where there is no local process, a platform secret. Every other owner is
432
+ an occupant of the ward, minted by the root alone.
433
+ 6. The owner creates and places. The work goes through an avatar.
434
+ 7. One avatar per client identity. Reconnect is not re-invite.
435
+ 8. Silence is never retried by a side.
436
+ 9. Envoys read secrets from the device, not from cells.
437
+ 10. Nothing under `packages/dock/` imports a private path of `packages/quo/src`,
438
+ only the four package exports; nothing under `estates/` imports a path of
439
+ `packages/dock/` or of `packages/quo/src`, only the two packages by name.
440
+ 11. Nothing here changes a harbor, a ward or a being.
441
+ 12. An estate needs nothing the dock does not give it. Its folder, copied
442
+ into an empty repository with one install, boots.
443
+
444
+ ## Where the dock stands
445
+
446
+ The one place under `packages/dock/` where a gap between these documents and the
447
+ folders is named. Each line is a debt to close, not a note to keep.
448
+
449
+ Two rules hold:
450
+
451
+ - **The ward and the being are asserted on the memory harbor, and every
452
+ harbor by the conformance suite.** The first keeps the network from
453
+ hiding a fault in the words: the memory harbor records its wire and
454
+ stands in for weather, so a door test that fails there fails at the door.
455
+ The second is how a harbor on any terrain is accepted, untouched, and not
456
+ before. Neither binds an adopter; they bind this bench.
457
+ - **One language, TypeScript, until 1.0.0.** A second kit is a second thing to
458
+ keep in step with every change, and the vectors under `packages/quo/vectors/`
459
+ are the hand that will be extended when the time comes.
460
+
461
+ The order of work, each step usable on its own and each proving a sentence
462
+ above or sending us back to fix it:
463
+
464
+ 1. Done. `packages/dock/` scaffold: its own package linking the library, the
465
+ four exports only, `check:dock` in the root gate, the terrain test pointed at
466
+ `packages/dock/beings`. The disk harbor in `packages/dock/harbor/disk.ts`:
467
+ one folder per ward holding seed, partition and the class source, a ward
468
+ record, the lease, and a save after every call. The `quo` CLI: init, serve,
469
+ census, boot, invite, knock, over the daemon's unix socket. Pilot and side
470
+ landed at steps 4 and 5.
471
+ 2. Done. The beings in `packages/dock/beings/`: `User`, `Desk`, `Avatar`, proven
472
+ on the memory harbor in `packages/dock/test/beings.test.ts`. A fake proof
473
+ becomes an invitation the user being minted, the avatar knocks and takes
474
+ her, and her tool list equals the user being's describe for her;
475
+ reconnect mints nothing; two clients see two tool lists; a push is a
476
+ reverse ask that reaches the side; remove is revocation. The ids that
477
+ carry the conventions: the desk is the occupant the root named `desk`
478
+ at the user being; the desk holds each user being as `user:<name>`; the
479
+ user being holds each device's way back as `to:<client>`; the avatar
480
+ holds the user being as `user` and is pushed to as `user:push`, two ids
481
+ because standings and occupants share one namespace.
482
+ 3. Done. The side in `packages/dock/beings/side.ts`: a function of the avatar
483
+ that runs until closed, and `word`, which tells the three words and the
484
+ object apart before any side renders them. The MCP server in
485
+ `packages/dock/mcp/server.ts`, proven over the SDK's in-memory transport in
486
+ `packages/dock/test/mcp.test.ts`: tools/list is her describe spoken as tools,
487
+ tools/call is a named ask, an object is a result, an error object and
488
+ the two words are error results with the text the trunk gives them, a
489
+ push is a logging notification, and a session closing and opening
490
+ reuses the standing. The SDK is the dock's first dependency, under
491
+ `packages/dock/` alone.
492
+ 4. Done. `quo side --as NAME` over stdio, proven in
493
+ `packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
494
+ process: the daemon finds or boots
495
+ `avatar:NAME`, enters her with a nonce the desk honours once, and runs
496
+ the model side beside her; the tool list is the user being's describe
497
+ for that client; a second start is a reconnect and mints nothing; a
498
+ `local` proof from anywhere else is refused. `quo init` now does the
499
+ root's setup, the desk public and one user being named by `--user`, so
500
+ a fresh folder is a world. Claude Code and Claude Desktop on the Mac
501
+ need only `quo serve` running and `quo side --as claude` as the server
502
+ command: no droplet, no OAuth.
503
+ 5. Done. `quo pilot` in `packages/dock/mcp/pilot.ts`, proven in
504
+ `packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
505
+ process: the owner's asks as tools and no more, census, boot, invite,
506
+ knock and, since step 16, remove, each
507
+ one an owner ask on the daemon's root socket, logged to stderr with
508
+ what it made. It is the root and not an avatar: nothing is booted for
509
+ it and no occupant is minted for it. With no daemon it says so and
510
+ exits. Claude Code as the owner is `claude mcp add` with
511
+ `quo pilot` as the command.
512
+ 6. Done. `quo serve --http PORT`: the HTTP door on loopback, `/health`
513
+ naming the wards, routes mounting by path so a hostname on the proxy is
514
+ a path on the daemon; proven in `packages/dock/test/cli.test.ts`. The systemd
515
+ unit and the Caddyfile are in `estates/lab/droplet/`. Disk store, the
516
+ lease and reboot from disk with relations intact were steps 1 and 4.
517
+ 7. Done. OAuth on the `mcp.` route in `packages/dock/mcp/oauth.ts`, mounted by
518
+ the daemon when `<dir>/routes.json` names the two public origins, and
519
+ proven over real HTTP in `packages/dock/test/oauth.test.ts`: metadata,
520
+ protected-resource metadata, dynamic registration, PKCE with S256, the
521
+ code grant, refresh with rotation, opaque tokens mapping a bearer to a
522
+ client identity, revoke by identity, and expiry. The conventions pinned
523
+ here: a pending request and a code live ten minutes, an access token
524
+ one hour, a refresh token thirty days; a code is spent the first time
525
+ it is offered, right or wrong; the client identity is chosen at the
526
+ allow page, not taken from the client's registration, because a client
527
+ like claude.ai registers a fresh client id on every connect and the
528
+ avatar must outlive that. The store is `<dir>/oauth.json`, the route's
529
+ own, mode 0600, and holds no secret of Quo's.
530
+ 8. Done. The exchange pages in `packages/dock/mcp/web/exchange.ts`, mounted on
531
+ the web route, proven over real HTTP in `packages/dock/test/web.test.ts`.
532
+ Login checks `QUO_OWNER_PASSWORD` in constant time and sets a signed
533
+ session cookie that lives ten minutes, the life of a request; with no
534
+ password set the exchange is closed. Allow names the client, where it
535
+ returns to, and the identity it will be, suggested from the client's
536
+ name and editable, one word and not the user or the desk. Allow admits
537
+ her through the one path every side uses: find or boot the avatar,
538
+ enter with a nonce the desk honours once under the `web` proof kind,
539
+ then complete the OAuth request with that identity. Deny sends the
540
+ client `access_denied`. Two pages, hand-written, rendering no blueprint.
541
+ 9. Done. The MCP endpoint in `packages/dock/mcp/http.ts` at `/mcp` on the mcp
542
+ route: streamable HTTP, a bearer names the identity, the identity names
543
+ her avatar through the same admit path, the side from step 3 runs
544
+ beside her, one session per connection, a session belonging to the
545
+ identity that opened it. Proven in `packages/dock/test/http.test.ts` with the
546
+ SDK's streamable HTTP client over the whole exchange. MCP is complete:
547
+ every human-assisted case on every device, with nothing of Quo's
548
+ crossing a machine.
549
+ 10. Done. The runner in `packages/dock/mcp/runner.ts`: the second side, an MCP
550
+ client written inline for a model that speaks function calling. Her
551
+ describe is a tools array, a tool call is a named ask, the three words
552
+ are tool messages, a line from the human or a push from the user being
553
+ starts a turn, and turns never overlap. Proven in
554
+ `packages/dock/test/runner.test.ts` over a scripted endpoint on loopback, and
555
+ live against Qwen3 Coder 30B in LM Studio on the Mac, which read her
556
+ describe, called `whoami` through the gate and answered with the id she
557
+ minted. What it taught: LM Studio refuses a parameters schema with no
558
+ `properties`, so the runner is where a describe is narrowed for an
559
+ endpoint, and nowhere else.
560
+ 11. Done. The agent side in `packages/dock/mcp/agent.ts`: the world is the loop.
561
+ A push from the user being is acknowledged at once by her `notify`,
562
+ lands in her inbox, which is the queue in her cells, and the side runs
563
+ it, one event at a time in order, then asks the user being back with
564
+ `report({ event, result })`, the ask this step added to her, gated to
565
+ devices. A run is a function of the event or a process in the agent's
566
+ folder with the event as its last argument, which is what `claude -p`
567
+ is. Proven in `packages/dock/test/agent.test.ts` with a fake run and a fake
568
+ process: the ask is answered before the run ends, a throw reports an
569
+ error object, and events that came while no side was up run when one
570
+ comes up. What it decided: the queue lives in the partition and not in
571
+ the side, so a restart loses nothing but the one run in flight. On a
572
+ device, `<dir>/agents.json` names each agent's identity, command and
573
+ folder, and the daemon admits her on the local proof and runs her side
574
+ beside her avatar; proven in the same suite over a disk harbor.
575
+ 12. The `quo.` route: Done. The reach interface with request and socket, since
576
+ the library move `packages/quo/src/harbor/reach.ts`, the disk harbor holds
577
+ the directory, and the daemon mounts `/quo` and dials with `--dial`. The
578
+ library's conformance suite passed untouched over two disk harbors on
579
+ loopback under both reaches, and the rendezvous carried a knock from a third
580
+ harbor to a dialer through its listener; `packages/dock/test/wire.test.ts`.
581
+ Live: a Mac daemon dialed `quo.lab.quo.systems`, an avatar there knocked at
582
+ the lab's user being across the internet, the lab knocked back over the held
583
+ socket and took `to:mac`, and a push from the lab landed in her inbox on the
584
+ Mac. What it taught: the proxy rewrites the route root with a trailing
585
+ slash, and a ward pk is 64 bytes on the wire;
586
+ `packages/dock/harbor/quo-harbor.md` has both.
587
+ 13. The browser harbor: Done. Harbor core carved out of the disk harbor
588
+ into a core of its own, since the library move `packages/quo/src/harbor/core.ts`,
589
+ the store a piece with files and IndexedDB under it, the dialer its
590
+ own terrain-free piece, the socket reach on the standard surface.
591
+ `packages/dock/harbor/browser.ts` is the core in a tab: IndexedDB, a web lock
592
+ for the lease, one socket per world.
593
+ The library's conformance suite passed untouched inside a real
594
+ Chromium against two browser harbors in one tab through a daemon on
595
+ loopback, migration between two databases included;
596
+ `packages/dock/test/terrain/browser.test.ts`, behind `check:terrain`. And the
597
+ first screen, `packages/dock/human/tab.ts`, rudimentary on purpose: a page on
598
+ the web route that boots a harbor in the tab, takes the owner password
599
+ once under the `tab` proof kind, and renders her describe as buttons,
600
+ with pushes landing on the page; a reload asks nothing. What it taught:
601
+ a tab's store keeps values through JSON because the cells guard refuses
602
+ structured clone; a side in a tab saves after the exchange and after
603
+ every call, as the daemon does; and a dialed socket is the fallback
604
+ from the moment it is dialed, because a ward asks the instant it boots.
605
+ 14. Owners at the door in the CLI: Done. The root socket takes `via`, a
606
+ standing the user being holds at another ward, and the daemon asks on
607
+ it in-process and saves; `quo pilot` and the bare owner asks take
608
+ `--via S`. Scene 8 on two daemons over a dialed socket in
609
+ `packages/dock/test/cli.test.ts`: the far root invites on its ward's pk, the
610
+ user being here knocks and takes `lab`, census and boot land there,
611
+ invite on the far pk is refused at the door, the standing survives a
612
+ restart, and the owner removed meets silence. What it taught: the
613
+ library had no owner ask that removes an owner, and the test reached
614
+ into the ward's cells; the word is `remove` now, decided in `packages/quo/SPEC.md`
615
+ at step 16.
616
+ 15. The human side, minimal: Done. Three pieces under `packages/dock/human/`:
617
+ `html.ts`, pure, a schema as a form, a form back as typed arguments,
618
+ an answer as a view, the three words with faces; `screen.ts`, the side
619
+ over a surface that can only show a page and hand back a form,
620
+ re-asking her describe after every call so the page follows the
621
+ digest; `dom.ts`, the surface on an element. The tab keeps its harbor
622
+ and exchange and hands the avatar to the side. Proven on the memory
623
+ harbor with a fake surface in `packages/dock/test/human.test.ts`, and in
624
+ Chromium behind `check:terrain`. Minimal on purpose: design, a
625
+ switcher, the guest's page and the native app are left to the human
626
+ document's own sitting, and none of them blocks a world from having
627
+ a screen.
628
+ 16. Removing: Done. `packages/quo/SPEC.md` gave the owner its fourth ask, `remove`,
629
+ the mirror of knock: a relation out of a being by id, occupant or
630
+ standing, and on the ward's own pk an owner, by the root alone. In
631
+ `packages/quo/src/ward/owner.ts` with its test; the pilot's fourth tool; `quo
632
+ remove` from the shell, the same word; the step 14 test asks it
633
+ instead of reaching into the cells. The lab's leftovers went with it,
634
+ recorded in `estates/lab/lab.md`.
635
+ 17. What the lab wants: Done. A launchd agent beside the systemd unit in
636
+ `estates/lab/droplet/`, the daemon as the user forever with
637
+ `~/.quo/dial.json` naming the routes it holds a socket to, installed
638
+ on the operator's Mac against the lab. And `quo run`: the runner from
639
+ a terminal, the same pipe as `quo side` with a model named in the
640
+ hello, the daemon running the runner beside the avatar, a line in a
641
+ turn and the final text a line out, proven with a scripted endpoint
642
+ as a child process in `packages/dock/test/cli.test.ts`. The gate profile per
643
+ client identity the road lists is defined nowhere and was not built.
644
+ 18. The edge terrain: Done. `packages/dock/harbor/edge/`, the core over a Durable
645
+ Object: its storage the store with the seed sealed under a platform
646
+ secret, the deployed worker the code, the object the lease; reached by
647
+ request, holding dialers' sockets, the root a route under a platform
648
+ secret. The conformance suite passed untouched inside the platform's
649
+ runtime, behind `check:terrain`; deployed to the lab's account and
650
+ piloted from the Mac over a standing, `--via edge`. The third terrain
651
+ the library move waited for; the `ws` question answered in the harbor
652
+ document. `estates/lab/lab.md` keeps the record, one slip included.
653
+
654
+ 19. The split, in flight: the estate folder became the dock, the lab's own
655
+ things moved to `estates/lab/`, its units and its document, and
656
+ the dock's package took its name. Then the repository took the shape
657
+ that says what ships by where it lives: `packages/quo/` and
658
+ `packages/dock/` are the two published packages, `estates/` is private,
659
+ and the root is nobody's package, the workspaces and the gate. The lab
660
+ redeployed from the new paths and a copy of `estates/lab/` booting in
661
+ an empty repository are what close it.
662
+ 20. The dock published: Done. `@quo-systems/dock` 0.1.0, the same shape
663
+ the library took, an emitted `dist/` with the `quo` command pointing
664
+ into it, the source and the four documents beside it, and
665
+ `packages/dock/test/package.test.ts` as the one suite that meets it as
666
+ an adopter: the library's tarball and this one installed into an empty
667
+ folder with nothing but Node, then `quo init`, a daemon, and
668
+ `quo census` answering with the desk and the user being. The library
669
+ went to 0.2.0 first, since the door's words moved the wire. What it
670
+ decided: the daemon bundles the tab from beside itself, emitted or
671
+ source, whichever is there, the one place the dock reads a file by
672
+ path at run time.
673
+
674
+ Steps 1 to 18 and 20 are in the tree and on the lab, and MCP is complete;
675
+ step 19 is in flight, and it is the one gap named today. `next.md` is where
676
+ the next sitting starts.
677
+
678
+ ## Glossary
679
+
680
+ - **avatar**: a being that is someone's voice from outside Quo.
681
+ - **client identity**: what a credential exchange establishes; one avatar.
682
+ - **envoy**: a being whose methods call a non-Quo thing.
683
+ - **dock**: what every estate needs and nobody writes twice, `packages/dock/`,
684
+ published as `@quo-systems/dock`.
685
+ - **estate**: all of one owner's harbors, with what is theirs alone, one
686
+ folder under `estates/`, never published. Also the agent under cron that
687
+ holds the owner pilot for one.
688
+ - **front desk**: the being every fresh client meets; hands out invitations
689
+ minted by user beings.
690
+ - **gate**: `for(occupant, asker)`; the whole permission model.
691
+ - **owner pilot**: `quo pilot`, the four owner asks over stdio, reaching
692
+ the daemon's socket; with `--via`, a ward elsewhere through a standing.
693
+ - **daemon**: `quo serve`, the one process on a device that holds a harbor.
694
+ - **proof**: what a route produces after authenticating; the front desk
695
+ trades it for an invitation.
696
+ - **route**: a hostname a reverse proxy sends to one process.
697
+ - **side**: the interface an avatar speaks through to someone outside Quo.
698
+ - **user being**: one per human, 24/7, holding all of the human's standings.