@quo-systems/dock 0.2.0 → 0.2.2

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/beings/avatar.ts +9 -3
  2. package/beings/carry.ts +101 -0
  3. package/beings/desk.ts +2 -2
  4. package/beings/index.ts +3 -1
  5. package/beings/link.ts +54 -0
  6. package/beings/look.ts +96 -0
  7. package/beings/quo-dock.md +163 -277
  8. package/beings/side.ts +10 -1
  9. package/beings/user.ts +67 -13
  10. package/cli/daemon.ts +58 -251
  11. package/cli/http.ts +70 -0
  12. package/cli/quo.ts +13 -5
  13. package/dist/beings/avatar.js +10 -3
  14. package/dist/beings/carry.d.ts +10 -0
  15. package/dist/beings/carry.js +106 -0
  16. package/dist/beings/desk.d.ts +1 -0
  17. package/dist/beings/desk.js +1 -1
  18. package/dist/beings/index.d.ts +2 -0
  19. package/dist/beings/index.js +3 -1
  20. package/dist/beings/link.d.ts +7 -0
  21. package/dist/beings/link.js +42 -0
  22. package/dist/beings/look.d.ts +27 -0
  23. package/dist/beings/look.js +71 -0
  24. package/dist/beings/side.d.ts +8 -1
  25. package/dist/beings/user.d.ts +29 -3
  26. package/dist/beings/user.js +71 -14
  27. package/dist/cli/daemon.d.ts +7 -19
  28. package/dist/cli/daemon.js +51 -240
  29. package/dist/cli/http.d.ts +17 -0
  30. package/dist/cli/http.js +60 -0
  31. package/dist/cli/quo.js +15 -5
  32. package/dist/harbor/quo.d.ts +4 -0
  33. package/dist/harbor/quo.js +53 -0
  34. package/dist/human/door.d.ts +5 -0
  35. package/dist/human/door.js +19 -0
  36. package/dist/human/guest.d.ts +3 -0
  37. package/dist/human/guest.js +25 -0
  38. package/dist/human/html.d.ts +10 -2
  39. package/dist/human/html.js +61 -10
  40. package/dist/human/screen.d.ts +8 -3
  41. package/dist/human/screen.js +26 -6
  42. package/dist/human/tab.d.ts +5 -0
  43. package/dist/human/tab.js +161 -43
  44. package/dist/human/web.d.ts +9 -0
  45. package/dist/human/web.js +95 -0
  46. package/dist/human/worlds.d.ts +10 -0
  47. package/dist/human/worlds.js +38 -0
  48. package/dist/mcp/http.d.ts +4 -3
  49. package/dist/mcp/http.js +6 -6
  50. package/dist/mcp/oauth.d.ts +9 -4
  51. package/dist/mcp/oauth.js +15 -14
  52. package/dist/mcp/pilot.js +1 -1
  53. package/dist/mcp/route.d.ts +12 -0
  54. package/dist/mcp/route.js +34 -0
  55. package/dist/mcp/server.d.ts +5 -10
  56. package/dist/mcp/server.js +35 -4
  57. package/dist/mcp/web/exchange.d.ts +5 -2
  58. package/dist/mcp/web/exchange.js +21 -7
  59. package/harbor/quo-harbor.md +67 -30
  60. package/harbor/quo.ts +67 -0
  61. package/human/door.ts +26 -0
  62. package/human/guest.ts +26 -0
  63. package/human/html.ts +58 -10
  64. package/human/quo-human.md +158 -67
  65. package/human/screen.ts +34 -8
  66. package/human/tab.ts +193 -53
  67. package/human/web.ts +123 -0
  68. package/human/worlds.ts +52 -0
  69. package/mcp/http.ts +10 -9
  70. package/mcp/oauth.ts +20 -17
  71. package/mcp/pilot.ts +1 -1
  72. package/mcp/quo-mcp.md +20 -2
  73. package/mcp/route.ts +39 -0
  74. package/mcp/server.ts +37 -18
  75. package/mcp/web/exchange.ts +23 -9
  76. package/package.json +2 -2
@@ -102,7 +102,10 @@ that every arrival is named and judged.
102
102
  - **Front desk.** A being on the user's harbor that strangers and fresh
103
103
  clients meet first. It holds a standing to every user being and can ask
104
104
  one for a device invitation. It is the only being that is ever public, and
105
- only for the first hello.
105
+ only for the first hello. Public is not a kind of being: she is an ordinary
106
+ being who chose to describe herself to strangers and answer them, and what
107
+ she keeps is hers like anyone's. This one keeps little because that is the
108
+ desk's job, not because Quo asks it of her.
106
109
  - **Envoy.** A being whose methods call a non-Quo thing and whose describe
107
110
  is that thing's surface. A SaaS, an MCP server someone else runs, an
108
111
  Arduino behind serial: each enters a world as a being like any other.
@@ -112,9 +115,40 @@ that every arrival is named and judged.
112
115
  - **Client identity.** What a credential exchange establishes: this human at
113
116
  this browser, this OAuth client for this human, this process on this
114
117
  machine. One client identity is one avatar, for life.
118
+ - **Carrier.** A being who shows the asks of the standings she holds as asks
119
+ of her own and forwards. The user being is one: a device the human let
120
+ reach sees acme's asks on her, and acme sees her.
121
+ - **Look.** What a being may say about how she is presented, as values: a
122
+ name, a logo, colours, a font, and a hint per ask. One optional ask,
123
+ `look`, answers it, and every side reads the part it understands.
115
124
  - **Route.** A hostname a reverse proxy sends to one process: `web.`, `quo.`,
116
125
  `mcp.`. Routes are deployment, never boundaries.
117
126
 
127
+ ## The ids
128
+
129
+ Every relation here is named by an id one being minted, and the dock's own
130
+ are these. They are conventions of this package and nothing of Quo's; a
131
+ different estate could choose others and change no word of the spec.
132
+
133
+ | id | who holds it | what it names |
134
+ | ------------------- | -------------- | ---------------------------------------- |
135
+ | `desk` | the user being | the front desk, her privileged occupant |
136
+ | `user:<name>` | the front desk | its standing to that human's user being |
137
+ | `<client>` | the user being | one device, by its client identity |
138
+ | `to:<client>` | the user being | her way back to that device |
139
+ | `user` | an avatar | her one standing, at the user being |
140
+ | `user:push` | an avatar | the user being as her occupant, to push |
141
+ | `avatar:<identity>` | the ward | the being key one client identity boots |
142
+
143
+ A device is two relations, because a relation in Quo is one direction: its
144
+ way in, the occupant, and her way back, `to:<client>`. Neither implies the
145
+ other and the door never joins them. The user being joins them herself, as
146
+ her own rule: she does not push to a device she no longer admits, and
147
+ `forget` drops both in one act. Her way back is handed over at hello and
148
+ taken when it can be: a knock back that is unreached, a tab whose pk the
149
+ far harbor has not bound yet or a phone in a tunnel, spends nothing, so she
150
+ keeps the invitation and takes it at the next push. A refusal is final.
151
+
118
152
  ## Architecture
119
153
 
120
154
  One droplet, one harbor, three routes. Every other placement is a subset.
@@ -122,7 +156,7 @@ One droplet, one harbor, three routes. Every other placement is a subset.
122
156
  ```
123
157
  acme.com, or razvan.com: the dialable part of an estate
124
158
  reverse proxy
125
- web. serves the bundle, runs the credential exchange, ends with an invitation
159
+ web. one page per world, /<ward>, the credential exchange, ends with an invitation
126
160
  quo. the harbor's socket door: the rendezvous for every dialer
127
161
  mcp. the model side over HTTP, a credential exchange in front
128
162
  quo serve: one process, one harbor, the ask pointer
@@ -164,6 +198,15 @@ standing to the user being is minted once and persists in the partition; a
164
198
  side opening and closing is a reconnect, and the keys carry on. Reconnect
165
199
  never re-invites.
166
200
 
201
+ An avatar is one standing, and a harbor holds as many avatars as it holds
202
+ relations; a ward only groups them. The droplet groups by client identity
203
+ per world, in the avatars ward. A tab groups by world, one local ward per
204
+ far world and one avatar per relation in it, so a fresh invitation is
205
+ always a fresh avatar and never a join on one that already holds hers: two
206
+ invitations into one world are two avatars, and two humans on one family
207
+ tablet are two relations. Which avatar a side speaks through is a switch
208
+ and nothing more.
209
+
167
210
  One class for every side. It has:
168
211
 
169
212
  - `answer`: the user being asks her only to push, and she forwards the push
@@ -202,8 +245,8 @@ last word. A push is the trigger of a turn, never a line smuggled into one:
202
245
  a push that arrives while a turn is running waits in her cells and starts
203
246
  the next turn when this one ends. Nothing is dropped and nothing is held;
204
247
  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.
248
+ turns, the world reaches a model as `packages/dock/mcp/quo-mcp.md` says: an
249
+ ask that starts a run, and a callback when it ends.
207
250
 
208
251
  One describe, many renderings. A screen is the user being's describe spoken
209
252
  as forms; a tool list is the same describe spoken as tools. Nobody builds a
@@ -211,6 +254,63 @@ UI and then a tool layer: they build asks, both fall out, and neither side
211
254
  can do a thing the other cannot, because the gate is one decision for
212
255
  describe and for dispatch.
213
256
 
257
+ ## Carrying
258
+
259
+ A device sees one describe, its avatar's one standing, and the human's
260
+ world is many standings: acme, the calendar, the house. The user being
261
+ carries them. Her describe for a device the human allowed to reach shows,
262
+ after her own asks, every ask of every standing she holds, each named
263
+ `<id>-<name>`, and answering one is an ask on that standing in her name. Acme
264
+ sees her id and never the device; the device sees acme without holding a
265
+ key anywhere; the human removes acme once, at her, and every device loses
266
+ it at once. Invariant 1 holds as written: the occupant still holds one
267
+ standing.
268
+
269
+ Carrying is a base, `beings/carry.ts`, and nothing in it knows what a user
270
+ is: a house being could carry its rooms, a company its suppliers. A subclass
271
+ says who may see what she carries, and the user being says a device with the
272
+ `reach` note, written when the human ticked the box at the exchange. The
273
+ notes of a carrier's describe say which asks are whose,
274
+ `standings: { id: { asks, look? } }`, so a screen draws one section per
275
+ standing and a model side keeps the flat list it needs; the mapping back
276
+ from a carried name to the standing is in her cells and never parsed from
277
+ the name. Her own asks come first and are never shadowed. She asks a
278
+ standing's describe again when its digest moved and its `look` once per
279
+ digest, so a describe of hers may cost one ask per stale standing, the price
280
+ of a page that is never more than one ask behind.
281
+
282
+ ## The look
283
+
284
+ Three sides, one describe, and nothing hard-wired for any of them. A being
285
+ who wants to say how she is presented answers one more ask, `look`, with
286
+ values and no more, and every side reads the part it understands. The
287
+ vocabulary is closed, in `beings/look.ts`, and a token that fails its shape
288
+ is dropped, not fixed:
289
+
290
+ | token | shape | a screen | a model side |
291
+ | ---------------------- | ------------------------------------- | --------------------------------- | -------------------------- |
292
+ | name | text, no tags | her heading | nothing |
293
+ | logo | an image as a data URI, never a URL | beside the heading | nothing |
294
+ | accent, background, fg | hex colours | CSS variables on her section only | nothing |
295
+ | font | a font stack, plain characters | her section | nothing |
296
+ | radius | 0 to 40 | her corners | nothing |
297
+ | order | her ask names | which come first | tool order |
298
+ | asks.NAME.title | text | the button | the tool's title |
299
+ | asks.NAME.icon | one short string | on the button | nothing |
300
+ | asks.NAME.readOnly | boolean | run on open, nothing to type | `readOnlyHint` |
301
+ | asks.NAME.destructive | boolean | ask once more | `destructiveHint` |
302
+ | asks.NAME.idempotent | boolean | offer a retry after silence | `idempotentHint` |
303
+
304
+ A look is not capability: it is never in a blueprint or a digest, Quo never
305
+ sees it, and a being without one renders and lists exactly as before. The
306
+ `look` ask itself is presentation and is neither a form nor a tool. A
307
+ carrier keeps each standing's look beside her asks in the notes, so acme's
308
+ section on the human's page is painted as acme asked, inside that section
309
+ and nowhere else. No token can carry a URL, a stylesheet or code, so a far
310
+ being cannot paint over the page or reach a server through it. A being who
311
+ marks an ask read-only and then writes has lied to her own page, and nothing
312
+ enforces it, the way nothing enforces that her empty ask is safe to repeat.
313
+
214
314
  ## Asks only
215
315
 
216
316
  There is no listening in Quo, and none under `packages/dock/`. A being who wants
@@ -219,8 +319,8 @@ whenever something happens. An event is an ask in the other direction, and
219
319
  it is the same act next door and across planets; only latency and
220
320
  reachability change. It follows that two beings never share memory, not
221
321
  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.
322
+ breaks the day one of them is somewhere else, and Quo exists so that nothing
323
+ breaks when she is. Only a ward moves; a being is placed once, at boot.
224
324
 
225
325
  Two consequences every side obeys:
226
326
 
@@ -258,22 +358,48 @@ thin as one call. Every route ends the same way:
258
358
  1. The route authenticates by its own means. Password, passkey, OAuth, a
259
359
  bearer token from a client, a nonce from a local process.
260
360
  2. The route asks the front desk `device({ proof })` and receives an
261
- invitation.
361
+ invitation. Which desk is which world: a harbor holds many wards, and
362
+ the human chooses the world at the allow page when there is more than
363
+ one; the grant remembers it.
262
364
  3. The route hands the invitation to the avatar it is about to boot, or to
263
365
  the one already booted for this client identity if it is a reconnect, in
264
366
  which case nothing is handed and the existing standing is used.
265
367
  4. The avatar knocks. The heir dies. From here there is no token anywhere.
266
368
 
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
369
+ The human decides two more things at step 1, on the same page that names
370
+ the identity: whether this device may **wake** the human's other devices,
371
+ and whether it may **reach** what she holds. Each word travels with the
372
+ proof, the user being writes it as a note on the occupant she mints, and her
373
+ gate reads the note: `push` is shown to the desk and to a device with the
374
+ `wake` note, and her standings are carried for a device with the `reach`
375
+ note, and for nobody else. A device that may wake can hand an agent an event
376
+ through the user being, and she sees who did; a device that may reach sees
377
+ acme on her, and a model sees acme only because the human said it may. The
378
+ notes are written when the occupant is minted; to change one, remove the
379
+ occupant and allow again, because reconnect mints nothing. A device the
380
+ root mints on her directly, with `quo invite`, carries no note: the id the
381
+ root chose is its client identity, and it reaches and may wake, since the
382
+ root owns everything she holds and chose to mint it.
383
+
384
+ Two proof kinds exist, and each arrives only the one way it is made:
385
+ `local`, a side that reached the daemon's side socket; `web`, a human who
386
+ passed the exchange pages. Each is a nonce the daemon minted for one kind
387
+ and honours once, under that kind alone, so a proof of any other provenance
388
+ names no nonce and is refused. A human at a screen passes no exchange: a
389
+ tab enters by a link, an invitation a user being minted, and nothing is
390
+ ever typed to get in. The root's first device is no exception; the root
391
+ mints its invitation with `quo invite` on the box and opens the link.
392
+
393
+ The route's own lifetimes, where it speaks OAuth: a pending request and a
394
+ code live ten minutes, an access token one hour, a refresh token thirty
395
+ days. A registered client lives ten minutes, and as long as its refresh
396
+ token once the human allowed it, since the register door is open to anyone
397
+ and what nobody allowed must not stay on disk. A code is spent the first
398
+ time it is offered, right or wrong. The client identity is chosen by the
399
+ human at the allow page and never taken from the client's registration: a
400
+ client may register afresh on every connect, and the avatar outlives that.
401
+
402
+ Tokens, cookies and grants exist for the length of step 1 above and are never
277
403
  stored on a Quo side. Revocation is `occupants.remove(id)` on the user being:
278
404
  the client's next ask, under the key the door bound for its avatar, hears
279
405
  `removed`, and the side speaks that in its own vocabulary: the MCP route
@@ -283,7 +409,11 @@ answers 401 at the next session and forgets the grant.
283
409
 
284
410
  One command, `quo`, under `packages/dock/cli/`. It imports the four library exports
285
411
  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.
412
+ through the ask pointer or an avatar, the two doors everyone has. The
413
+ daemon's folder holds the process and its doors, the two sockets and the
414
+ HTTP listener; each route on that listener lives in the folder whose truth
415
+ it is, `human/web.ts`, `mcp/route.ts`, `harbor/quo.ts`, and the daemon only
416
+ mounts them.
287
417
 
288
418
  ```
289
419
  quo init [--dir D] mint a seed, boot a harbor and one ward over a disk store, write both
@@ -292,7 +422,7 @@ quo pilot [--dir D] [--via S] the owner pilot over stdio: the ward's describe as
292
422
  quo side [--dir D] --as NAME an avatar over stdio for a local client; NAME is the client identity
293
423
  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
424
  quo census [--dir D] print the census once and exit
295
- quo boot|public|invite|knock|remove the five owner asks from a shell, for scripts and for agents under cron; --via S sends them to another ward
425
+ quo boot|public|invite|knock|remove|unboot the six owner asks from a shell, for scripts and for agents under cron; --via S sends them to another ward
296
426
  quo reach <pk> <url> a hint for the harbor's directory: that world's ward lives at that quo. route
297
427
  quo estate <dir> --domain D an estate folder of the shape every estate has: a droplet's quo directory, its unit and Caddyfile, a package on this dock
298
428
  ```
@@ -393,8 +523,8 @@ to be decided.
393
523
 
394
524
  ## Generality
395
525
 
396
- Six general pieces exist under `packages/dock/`, and everything concrete is an
397
- implementation of one of them. An estate implements none of them: it holds
526
+ Eight general pieces exist under `packages/dock/`, and everything concrete is
527
+ an implementation of one of them. An estate implements none of them: it holds
398
528
  class bodies and placement, and reaches every piece through the dock.
399
529
 
400
530
  | piece | one interface for | implementations |
@@ -405,6 +535,8 @@ class bodies and placement, and reaches every piece through the dock.
405
535
  | side | an avatar speaking to someone outside | screen, MCP server, runner, agent |
406
536
  | proof | turning a credential into an invitation | one verifier, a daemon nonce, three kinds |
407
537
  | envoy | a non-Quo surface as a being | none built: a name until its first user |
538
+ | carrier | a being showing her standings as her own | the user being; any estate being that extends it |
539
+ | look | how a being is presented, as values | read by the screen and the model side |
408
540
 
409
541
  Two rules keep them general:
410
542
 
@@ -415,8 +547,8 @@ Two rules keep them general:
415
547
 
416
548
  Harbor core, reach and store held across three terrains and moved into
417
549
  `packages/quo/src` with `packages/quo/SPEC.md` in the same commit on 2026-09-04;
418
- what stays here of them is each terrain's store, loader, lease and listener. The
419
- other three are dock conventions and stay here.
550
+ what stays here of them is each terrain's store, loader, lease and listener.
551
+ The other five are dock conventions and stay here.
420
552
 
421
553
  ## Invariants
422
554
 
@@ -444,8 +576,11 @@ other three are dock conventions and stay here.
444
576
 
445
577
  ## Where the dock stands
446
578
 
447
- The one place under `packages/dock/` where a gap between these documents and the
448
- folders is named. Each line is a debt to close, not a note to keep.
579
+ The one place under `packages/dock/` where a gap between these documents and
580
+ the folders is named. Each line is a debt to close, not a note to keep.
581
+
582
+ No gap today. Everything these documents describe is in the folders and on
583
+ the machines, and MCP is complete.
449
584
 
450
585
  Two rules hold:
451
586
 
@@ -459,262 +594,12 @@ Two rules hold:
459
594
  keep in step with every change, and the vectors under `packages/quo/vectors/`
460
595
  are the hand that will be extended when the time comes.
461
596
 
462
- The order of work, each step usable on its own and each proving a sentence
463
- above or sending us back to fix it:
464
-
465
- 1. Done. `packages/dock/` scaffold: its own package linking the library, the
466
- four exports only, `check:dock` in the root gate, the terrain test pointed at
467
- `packages/dock/beings`. The disk harbor in `packages/dock/harbor/disk.ts`:
468
- one folder per ward holding seed, partition and the class source, a ward
469
- record, the lease, and a save after every call. The `quo` CLI: init, serve,
470
- census, boot, invite, knock, over the daemon's unix socket. Pilot and side
471
- landed at steps 4 and 5.
472
- 2. Done. The beings in `packages/dock/beings/`: `User`, `Desk`, `Avatar`, proven
473
- on the memory harbor in `packages/dock/test/beings.test.ts`. A fake proof
474
- becomes an invitation the user being minted, the avatar knocks and takes
475
- her, and her tool list equals the user being's describe for her;
476
- reconnect mints nothing; two clients see two tool lists; a push is a
477
- reverse ask that reaches the side; remove is revocation. The ids that
478
- carry the conventions: the desk is the occupant the root named `desk`
479
- at the user being; the desk holds each user being as `user:<name>`; the
480
- user being holds each device's way back as `to:<client>`; the avatar
481
- holds the user being as `user` and is pushed to as `user:push`, two ids
482
- because standings and occupants share one namespace.
483
- 3. Done. The side in `packages/dock/beings/side.ts`: a function of the avatar
484
- that runs until closed, and `word`, which tells the three words and the
485
- object apart before any side renders them. The MCP server in
486
- `packages/dock/mcp/server.ts`, proven over the SDK's in-memory transport in
487
- `packages/dock/test/mcp.test.ts`: tools/list is her describe spoken as tools,
488
- tools/call is a named ask, an object is a result, an error object and
489
- the two words are error results with the text the trunk gives them, a
490
- push is a logging notification, and a session closing and opening
491
- reuses the standing. The SDK is the dock's first dependency, under
492
- `packages/dock/` alone.
493
- 4. Done. `quo side --as NAME` over stdio, proven in
494
- `packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
495
- process: the daemon finds or boots
496
- `avatar:NAME`, enters her with a nonce the desk honours once, and runs
497
- the model side beside her; the tool list is the user being's describe
498
- for that client; a second start is a reconnect and mints nothing; a
499
- `local` proof from anywhere else is refused. `quo init` now does the
500
- root's setup, the desk public and one user being named by `--user`, so
501
- a fresh folder is a world. Claude Code and Claude Desktop on the Mac
502
- need only `quo serve` running and `quo side --as claude` as the server
503
- command: no droplet, no OAuth.
504
- 5. Done. `quo pilot` in `packages/dock/mcp/pilot.ts`, proven in
505
- `packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
506
- process: the model side of step 3 over the ward's owner asks, the
507
- ward's describe as tools and no list of its own, each call an owner ask
508
- on the daemon's root socket, and nothing logged. It is the root and not
509
- an avatar: nothing is booted for it and no occupant is minted for it.
510
- With no daemon it says so and exits. Claude Code as the owner is
511
- `claude mcp add` with `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 registered client lives ten
525
- minutes, and as long as its refresh token once the human allowed it,
526
- since the register door is open to anyone and what nobody allowed must
527
- not stay on disk; a code is spent the first time
528
- it is offered, right or wrong; the client identity is chosen at the
529
- allow page, not taken from the client's registration, because a client
530
- like claude.ai registers a fresh client id on every connect and the
531
- avatar must outlive that. The store is `<dir>/oauth.json`, the route's
532
- own, mode 0600, and holds no secret of Quo's.
533
- 8. Done. The exchange pages in `packages/dock/mcp/web/exchange.ts`, mounted on
534
- the web route, proven over real HTTP in `packages/dock/test/web.test.ts`.
535
- Login checks `QUO_OWNER_PASSWORD` in constant time and sets a signed
536
- session cookie that lives ten minutes, the life of a request; with no
537
- password set the exchange is closed. Allow names the client, where it
538
- returns to, and the identity it will be, suggested from the client's
539
- name and editable, one word and not the user or the desk. Allow admits
540
- her through the one path every side uses: find or boot the avatar,
541
- enter with a nonce the desk honours once under the `web` proof kind,
542
- then complete the OAuth request with that identity. Deny sends the
543
- client `access_denied`. Two pages, hand-written, rendering no blueprint.
544
- 9. Done. The MCP endpoint in `packages/dock/mcp/http.ts` at `/mcp` on the mcp
545
- route: streamable HTTP, a bearer names the identity, the identity names
546
- her avatar through the same admit path, the side from step 3 runs
547
- beside her, one session per connection, a session belonging to the
548
- identity that opened it. Proven in `packages/dock/test/http.test.ts` with the
549
- SDK's streamable HTTP client over the whole exchange. MCP is complete:
550
- every human-assisted case on every device, with nothing of Quo's
551
- crossing a machine.
552
- 10. Done. The runner in `packages/dock/mcp/runner.ts`: the second side, an MCP
553
- client written inline for a model that speaks function calling. Her
554
- describe is a tools array, a tool call is a named ask, the three words
555
- are tool messages, a line from the human or a push from the user being
556
- starts a turn, and turns never overlap. Proven in
557
- `packages/dock/test/runner.test.ts` over a scripted endpoint on loopback, and
558
- live against Qwen3 Coder 30B in LM Studio on the Mac, which read her
559
- describe, called `whoami` through the gate and answered with the id she
560
- minted. What it taught: LM Studio refuses a parameters schema with no
561
- `properties`, so the runner is where a describe is narrowed for an
562
- endpoint, and nowhere else.
563
- 11. Done. The agent side in `packages/dock/mcp/agent.ts`: the world is the loop.
564
- A push from the user being is acknowledged at once by her `notify`,
565
- lands in her inbox, which is the queue in her cells, and the side runs
566
- it, one event at a time in order, then asks the user being back with
567
- `report({ event, result })`, the ask this step added to her, gated to
568
- devices. A run is a function of the event or a process in the agent's
569
- folder with the event as its last argument, which is what `claude -p`
570
- is. Proven in `packages/dock/test/agent.test.ts` with a fake run and a fake
571
- process: the ask is answered before the run ends, a throw reports an
572
- error object, and events that came while no side was up run when one
573
- comes up. What it decided: the queue lives in the partition and not in
574
- the side, so a restart loses nothing but the one run in flight. On a
575
- device, `<dir>/agents.json` names each agent's identity, command and
576
- folder, and the daemon admits her on the local proof and runs her side
577
- beside her avatar; proven in the same suite over a disk harbor.
578
- 12. The `quo.` route: Done. The reach interface with request and socket, since
579
- the library move `packages/quo/src/harbor/reach.ts`, the disk harbor holds
580
- the directory, and the daemon mounts `/quo` and dials with `--dial`. The
581
- library's conformance suite passed untouched over two disk harbors on
582
- loopback under both reaches, and the rendezvous carried a knock from a third
583
- harbor to a dialer through its listener; `packages/dock/test/wire.test.ts`.
584
- Live: a Mac daemon dialed `quo.lab.quo.systems`, an avatar there knocked at
585
- the lab's user being across the internet, the lab knocked back over the held
586
- socket and took `to:mac`, and a push from the lab landed in her inbox on the
587
- Mac. What it taught: the proxy rewrites the route root with a trailing
588
- slash, and a ward pk is 64 bytes on the wire;
589
- `packages/dock/harbor/quo-harbor.md` has both.
590
- 13. The browser harbor: Done. Harbor core carved out of the disk harbor
591
- into a core of its own, since the library move `packages/quo/src/harbor/core.ts`,
592
- the store a piece with files and IndexedDB under it, the dialer its
593
- own terrain-free piece, the socket reach on the standard surface.
594
- `packages/dock/harbor/browser.ts` is the core in a tab: IndexedDB, a web lock
595
- for the lease, one socket per world.
596
- The library's conformance suite passed untouched inside a real
597
- Chromium against two browser harbors in one tab through a daemon on
598
- loopback, migration between two databases included;
599
- `packages/dock/test/terrain/browser.test.ts`, behind `check:terrain`. And the
600
- first screen, `packages/dock/human/tab.ts`, rudimentary on purpose: a page on
601
- the web route that boots a harbor in the tab, takes the owner password
602
- once under the `tab` proof kind, and renders her describe as buttons,
603
- with pushes landing on the page; a reload asks nothing. What it taught:
604
- a tab's store keeps values through JSON because the cells guard refuses
605
- structured clone; a side in a tab saves after the exchange and after
606
- every call, as the daemon does; and a dialed socket is the fallback
607
- from the moment it is dialed, because a ward asks the instant it boots.
608
- 14. Owners at the door in the CLI: Done. The root socket takes `via`, a
609
- standing the user being holds at another ward, and the daemon asks on
610
- it in-process and saves; `quo pilot` and the bare owner asks take
611
- `--via S`. Scene 8 on two daemons over a dialed socket in
612
- `packages/dock/test/cli.test.ts`: the far root invites on its ward's pk, the
613
- user being here knocks and takes `lab`, census and boot land there,
614
- invite on the far pk is refused at the door, the standing survives a
615
- restart, and the owner removed meets silence. What it taught: the
616
- library had no owner ask that removes an owner, and the test reached
617
- into the ward's cells; the word is `remove` now, decided in `packages/quo/SPEC.md`
618
- at step 16.
619
- 15. The human side, minimal: Done. Three pieces under `packages/dock/human/`:
620
- `html.ts`, pure, a schema as a form, a form back as typed arguments,
621
- an answer as a view, the three words with faces; `screen.ts`, the side
622
- over a surface that can only show a page and hand back a form,
623
- re-asking her describe after every call so the page follows the
624
- digest; `dom.ts`, the surface on an element. The tab keeps its harbor
625
- and exchange and hands the avatar to the side. Proven on the memory
626
- harbor with a fake surface in `packages/dock/test/human.test.ts`, and in
627
- Chromium behind `check:terrain`. Minimal on purpose: design, a
628
- switcher, the guest's page and the native app are left to the human
629
- document's own sitting, and none of them blocks a world from having
630
- a screen.
631
- 16. Removing: Done. `packages/quo/SPEC.md` gave the owner its fourth ask, `remove`,
632
- the mirror of knock: a relation out of a being by id, occupant or
633
- standing, and on the ward's own pk an owner, by the root alone. In
634
- `packages/quo/src/ward/owner.ts` with its test; the pilot's fourth tool; `quo
635
- remove` from the shell, the same word; the step 14 test asks it
636
- instead of reaching into the cells. The lab's leftovers went with it,
637
- recorded in `estates/lab/lab.md`.
638
- 17. What the lab wants: Done. A launchd agent beside the systemd unit in
639
- `estates/lab/droplet/`, the daemon as the user forever with
640
- `~/.quo/dial.json` naming the routes it holds a socket to, installed
641
- on the operator's Mac against the lab. And `quo run`: the runner from
642
- a terminal, the same pipe as `quo side` with a model named in the
643
- hello, the daemon running the runner beside the avatar, a line in a
644
- turn and the final text a line out, proven with a scripted endpoint
645
- as a child process in `packages/dock/test/cli.test.ts`. The gate profile per
646
- client identity the road lists is defined nowhere and was not built.
647
- 18. The edge terrain: Done. `packages/dock/harbor/edge/`, the core over a Durable
648
- Object: its storage the store with the seed sealed under a platform
649
- secret, the deployed worker the code, the object the lease; reached by
650
- request, holding dialers' sockets, the root a route under a platform
651
- secret. The conformance suite passed untouched inside the platform's
652
- runtime, behind `check:terrain`; deployed to the lab's account and
653
- piloted from the Mac over a standing, `--via edge`. The third terrain
654
- the library move waited for; the `ws` question answered in the harbor
655
- document. `estates/lab/lab.md` keeps the record, one slip included.
656
-
657
- 19. The split: Done. The estate folder became the dock, the lab's own
658
- things moved to `estates/lab/`, and the dock's package took its name.
659
- Then the repository took the shape that says what ships by where it
660
- lives: `packages/quo/` and `packages/dock/` are the two published
661
- packages, `estates/` is private, and the root is nobody's package, the
662
- workspaces and the gate. The lab is an estate folder of the shape this
663
- document names, one folder per harbor, and `estates/lab/test/boot.test.ts`
664
- is invariant 12 proven: a copy of the folder, the two packages installed
665
- into it and nothing else, and the droplet boots. The lab redeployed
666
- from it: the droplet from a clean export of HEAD with its unit copied
667
- from the estate's folder, the edge from the estate's own config
668
- resolving the dock's worker by name, the Mac's agent on the new path.
669
- What it taught: a nested npm under `npm run` inherits the outer run's
670
- local prefix by environment and installs into the repository instead
671
- of the empty folder, so the stranger tests keep nothing of it; and
672
- `npx` in a workspace runs at the package root, so an estate names its
673
- edge config from its own folder.
674
- 20. The dock published: Done. `@quo-systems/dock` 0.1.0, the same shape
675
- the library took, an emitted `dist/` with the `quo` command pointing
676
- into it, the source and the four documents beside it, and
677
- `packages/dock/test/package.test.ts` as the one suite that meets it as
678
- an adopter: the library's tarball and this one installed into an empty
679
- folder with nothing but Node, then `quo init`, a daemon, and
680
- `quo census` answering with the desk and the user being. The library
681
- went to 0.2.0 first, since the door's words moved the wire. What it
682
- decided: the daemon bundles the tab from beside itself, emitted or
683
- source, whichever is there, the one place the dock reads a file by
684
- path at run time.
685
-
686
- 21. A second estate: Done, and live. `estates/gherghina/`,
687
- staging like the lab and nothing more, one harbor on the shared droplet
688
- `nervur-staging-0`, routes under `gherghina.quo.systems`, no agents and
689
- no classes of its own yet, the same unit and Caddyfile shape as the
690
- lab's. It is the first proof that the dock has two users: its folder
691
- depends on the dock alone, and `estates/gherghina/test/boot.test.ts`
692
- runs the one proof every estate here runs, `estates/proof.ts`, shared
693
- between them and shipped nowhere. The three staging droplets are shared
694
- by every estate in this repository, and none of them is production or
695
- holds real data. Its droplet stood up the same day, its document says
696
- how; what it taught went into both estates' units: the agent's folders
697
- are optional to the daemon, since a harbor may have no agent.
698
-
699
- 22. The scaffold: Done. `quo estate <dir> --domain D` writes an estate
700
- folder of the shape the two estates here agreed on: a package file
701
- depending on this dock and nothing else, one document to fill in, and
702
- `droplet/` as the first harbor, its routes under the domain, no
703
- agents, no classes of its own, and that device's unit and Caddyfile
704
- from templates the dock ships beside its command. The unit it writes
705
- runs the installed dock from the estate's folder on the box, since an
706
- adopter has no tree there; the two estates here run the tree. Proven
707
- in `packages/dock/test/cli.test.ts`: the folder is written, refuses to
708
- overwrite a package or take a name that is no domain, and its droplet
709
- boots, `quo init` into it and a daemon answering the census.
710
-
711
- Steps 1 to 22 are in the tree and on the machines, and MCP is complete;
712
- the documents and the folders agree up to step 22. There is no gap named
713
- today. `next.md` is where the next sitting starts.
597
+ `next.md` is where the next sitting starts.
714
598
 
715
599
  ## Glossary
716
600
 
717
601
  - **avatar**: a being that is someone's voice from outside Quo.
602
+ - **carrier**: a being who shows her standings' asks as her own and forwards.
718
603
  - **client identity**: what a credential exchange establishes; one avatar.
719
604
  - **envoy**: a being whose methods call a non-Quo thing.
720
605
  - **dock**: what every estate needs and nobody writes twice, `packages/dock/`,
@@ -725,6 +610,7 @@ today. `next.md` is where the next sitting starts.
725
610
  - **front desk**: the being every fresh client meets; hands out invitations
726
611
  minted by user beings.
727
612
  - **gate**: `for(occupant, asker)`; the whole permission model.
613
+ - **look**: how a being is presented, as values; one optional ask.
728
614
  - **owner pilot**: `quo pilot`, the model side over a ward's owner asks, reaching
729
615
  the daemon's socket; with `--via`, a ward elsewhere through a standing.
730
616
  - **daemon**: `quo serve`, the one process on a device that holds a harbor.
package/beings/side.ts CHANGED
@@ -6,13 +6,22 @@
6
6
  // a screen, an MCP server, a runner, an event-driven agent. None knows
7
7
  // another exists, and none of this file names a platform.
8
8
  import { isSilence, isUnreached, isWord, wordOf } from '@quo-systems/quo';
9
- import type { Answer, Json, JsonObject, WordName } from '@quo-systems/quo';
9
+ import type { Answer, Blueprint, Json, JsonObject, Wanted, WordName } from '@quo-systems/quo';
10
10
  import type { Avatar } from './avatar.ts';
11
11
 
12
12
  // A side, running: it holds the avatar until closed.
13
13
  export type Serving = { close(): Promise<void> };
14
14
  export type Side = (avatar: Avatar) => Promise<Serving>;
15
15
 
16
+ // What a side speaks for: the empty ask and a named one, and the ears a
17
+ // push reaches. An avatar on her one standing is one; the owner's asks on a
18
+ // ward are another; a guest at a world's public being is a third.
19
+ export type Subject = {
20
+ tools(): Promise<Blueprint | { error: string }>;
21
+ call(name: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
22
+ ears: Set<(object: JsonObject) => void>;
23
+ };
24
+
16
25
  // The three words for "no object", and the object itself, as every side must
17
26
  // tell them apart before rendering. An error object is an ordinary answer
18
27
  // that the side may mark; silence and unreached are not objects at all.