your-attention 0.0.0-stage → 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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/PRINCIPLES.md +283 -0
  3. package/README.md +418 -2
  4. package/bin/your-attention.js +3 -0
  5. package/package.json +82 -4
  6. package/src/adapters/files.ts +66 -0
  7. package/src/adapters/mdns.ts +53 -0
  8. package/src/adapters/mermaid.ts +50 -0
  9. package/src/adapters/openui-body.ts +40 -0
  10. package/src/adapters/openui-library.ts +3 -0
  11. package/src/adapters/sqlite.ts +68 -0
  12. package/src/cli.ts +814 -0
  13. package/src/client.ts +459 -0
  14. package/src/cloudflare/collector.ts +53 -0
  15. package/src/cloudflare/inbox-object.ts +338 -0
  16. package/src/cloudflare/storage.ts +151 -0
  17. package/src/cloudflare/worker.ts +189 -0
  18. package/src/core/crypto.ts +23 -0
  19. package/src/core/diagrams.ts +225 -0
  20. package/src/core/expr.ts +125 -0
  21. package/src/core/file-links.ts +48 -0
  22. package/src/core/hooks.ts +74 -0
  23. package/src/core/http.ts +651 -0
  24. package/src/core/inbox.ts +2463 -0
  25. package/src/core/pin.ts +234 -0
  26. package/src/core/sealed.ts +167 -0
  27. package/src/core/session-link.ts +13 -0
  28. package/src/core/socket.ts +88 -0
  29. package/src/core/storage.ts +102 -0
  30. package/src/core/summary.ts +149 -0
  31. package/src/core/telemetry.ts +221 -0
  32. package/src/core/types.ts +470 -0
  33. package/src/core/version.ts +2 -0
  34. package/src/core/widgets.ts +128 -0
  35. package/src/extensions/triage.ts +232 -0
  36. package/src/integrations/claude/ask-hook.ts +84 -0
  37. package/src/integrations/claude/channel-flag.ts +102 -0
  38. package/src/integrations/claude/channel.ts +276 -0
  39. package/src/integrations/claude/session-name.ts +32 -0
  40. package/src/integrations/claude/turn-end-hook.ts +84 -0
  41. package/src/integrations/herdr/approve.ts +71 -0
  42. package/src/integrations/herdr/blocked.ts +210 -0
  43. package/src/integrations/herdr/dialog.ts +64 -0
  44. package/src/integrations/herdr/focus.ts +31 -0
  45. package/src/integrations/herdr/place.ts +22 -0
  46. package/src/integrations/images.ts +43 -0
  47. package/src/integrations/opencode/install.ts +24 -0
  48. package/src/integrations/opencode/plugin.ts +157 -0
  49. package/src/integrations/pi/extension.ts +166 -0
  50. package/src/integrations/run-tool.ts +157 -0
  51. package/src/integrations/secrets.ts +63 -0
  52. package/src/integrations/tags.ts +27 -0
  53. package/src/integrations/tools.ts +147 -0
  54. package/src/node/home.ts +20 -0
  55. package/src/node/install.ts +225 -0
  56. package/src/node/links.ts +249 -0
  57. package/src/node/pin-prompt.ts +125 -0
  58. package/src/node/server.ts +320 -0
  59. package/src/node/service.ts +74 -0
  60. package/src/node/telemetry.ts +127 -0
  61. package/src/owner-watch.ts +49 -0
  62. package/src/tui/api.ts +203 -0
  63. package/src/tui/app.ts +586 -0
  64. package/src/tui/body.ts +727 -0
  65. package/src/tui/cardview.ts +453 -0
  66. package/src/tui/doc.ts +157 -0
  67. package/src/tui/edit.ts +119 -0
  68. package/src/tui/input.ts +192 -0
  69. package/src/tui/md.ts +196 -0
  70. package/src/tui/screen.ts +225 -0
  71. package/src/tui/terminal.ts +130 -0
  72. package/src/tui/text.ts +103 -0
  73. package/src/tui/theme.ts +105 -0
  74. package/src/tui/view.ts +314 -0
  75. package/src/watch.ts +104 -0
  76. package/web/age.d.ts +2 -0
  77. package/web/age.js +16 -0
  78. package/web/answers.d.ts +37 -0
  79. package/web/answers.js +485 -0
  80. package/web/app.js +739 -0
  81. package/web/body-faults.d.ts +7 -0
  82. package/web/body-faults.js +69 -0
  83. package/web/components.js +923 -0
  84. package/web/copy.js +118 -0
  85. package/web/details.d.ts +20 -0
  86. package/web/details.js +59 -0
  87. package/web/env.js +97 -0
  88. package/web/favicon-waiting.svg +1 -0
  89. package/web/favicon.svg +1 -0
  90. package/web/index.html +16 -0
  91. package/web/library-spec.js +33 -0
  92. package/web/markdown.d.ts +16 -0
  93. package/web/markdown.js +142 -0
  94. package/web/paste-images.d.ts +17 -0
  95. package/web/paste-images.js +193 -0
  96. package/web/pin-key.d.ts +4 -0
  97. package/web/pin-key.js +125 -0
  98. package/web/pin.js +126 -0
  99. package/web/queue.d.ts +50 -0
  100. package/web/queue.js +97 -0
  101. package/web/sealed.d.ts +2 -0
  102. package/web/sealed.js +13 -0
  103. package/web/search.d.ts +11 -0
  104. package/web/search.js +70 -0
  105. package/web/stories/cards.stories.js +144 -0
  106. package/web/stories/page.stories.js +54 -0
  107. package/web/stories/render.js +16 -0
  108. package/web/style.css +417 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nicolas Marchildon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PRINCIPLES.md ADDED
@@ -0,0 +1,283 @@
1
+ # Design principles
2
+
3
+ Each principle states what Your Attention does, with its reason below it.
4
+
5
+ The number in a heading, such as DP7, is the stable handle that this repository and its pull requests use to cite a principle.
6
+
7
+ Every change to this repo is checked against these principles. A change that breaks one either changes the code or amends the principle in the same pull request.
8
+
9
+ ## Card format
10
+
11
+ ### DP1: Each card body is free-form widgets that fit its question
12
+
13
+ The agent picks the widgets that fit the question. A code review gets a diff, a comparison gets a table, numbers get a chart, and a choice gets option cards or a slider. Cards are generative UI written in OpenUI Lang, and by default the renderer accepts only widgets from the library. If you turn on the option, an agent can also send its own scripts and raw HTML. These run in a sandboxed frame that cannot read the inbox or your other cards. A card can also come with up to 10 files, such as images or PDF documents. The inbox shows each file with a renderer for its type.
14
+
15
+ The right widget makes a card fast to answer, and some cards need more than the library offers, under a sandbox you choose to allow.
16
+
17
+ ### DP2: Agents keep card templates and revise them from your feedback
18
+
19
+ An agent saves a card format that works as a template. When you comment on a format, the agent writes a new template version and records your feedback with it. Agents manage templates first. Browsing them in the inbox is possible, but it comes later.
20
+
21
+ Cards get easier to answer over time, and agents do that work for you.
22
+
23
+ ### DP3: Open pages update themselves
24
+
25
+ When a card, a template or the page code changes, the inbox pushes the change to every open page. The update keeps the drafts you have typed.
26
+
27
+ You should never have to reload.
28
+
29
+ ### DP4: A follow-up is a new card, linked both ways to the one before
30
+
31
+ When an agent revises a card or needs you again, it sends a new card. The new card links back to the card it follows, and the old card links forward to its follow-up, so you can walk the chain in both directions. The card just before appears collapsed below the new one, with your answer, in the layout you answered it in.
32
+
33
+ Cards stay short-lived, and the links let you trace a decision without a chat thread.
34
+
35
+ ### DP5: A new card never replaces the one you are reading
36
+
37
+ The card you have open remains on screen until you move on from it, by answering, skipping, picking another card, or closing the page. A new card joins the queue in its priority slot, above your card if it ranks higher, with a mark that it is new.
38
+
39
+ A card that changes under you loses your place and your train of thought.
40
+
41
+ ### DP6: Cards link to other cards in their text
42
+
43
+ A card can link to one or many other cards inside its question, as hypertext. A link always reads as the linked card full title, never a bare id. Hovering a link shows a preview of the card, or at least its title, and a click opens it in place. The inbox shows which cards link to the one you have open, and the follow-up chain is one kind of link among these.
44
+
45
+ Decisions depend on each other, and a link you can read and preview costs less attention than one you must open.
46
+
47
+ ### DP7: Agents edit a card with small patches
48
+
49
+ To change a card, an agent sends edits against the card source, the way a coding agent edits a file with its patch tool: replace this exact text with that one. The card is a virtual file, so agents can reuse the editing habits they already have. A long card, such as a design spec, then costs only the tokens of the change. The inbox keeps each version, and you see what changed.
50
+
51
+ Resending a large card for each small edit wastes tokens and hides what changed.
52
+
53
+ ### DP8: Secrets go to the agent sealed, and never into the conversation
54
+
55
+ The secret widget requests one value, such as an API key, on a card of any kind. The card contains a public key. The matching private key stays in the memory of the session that posted the card. Your browser encrypts the value with it before it leaves the page, so the inbox stores ciphertext only. The agent opens the value into a file that only your user account can read, and the model gets the path of the file. The value itself never appears in a transcript or a log.
56
+
57
+ Agents need keys to finish their work, and a key typed into a chat remains in its transcript after the session ends.
58
+
59
+ ## Card quality
60
+
61
+ ### DP9: Triage checks cards before you see them
62
+
63
+ Triage is a pluggable extension that checks each card for a goal, the options that the agent weighed, evidence, and a recommendation, and sends back a card that lacks them. The default approver signs in with OpenRouter OAuth and bills your own OpenRouter account, so setup takes one click. Instead, it can use one of your coding subscriptions and run Codex, Claude Code, pi, or Cursor in print mode. Triage runs as its own service, such as a systemd or launchd unit, that listens for new cards. When it is offline, cards go straight to you, and when it reconnects it screens the cards still waiting.
64
+
65
+ Your attention is the scarce input, and onboarding must be easy. Triage must never stand between you and a card when it is down.
66
+
67
+ ### DP10: Each card includes a recommendation
68
+
69
+ The agent says what it would do and why. You accept it as it is, or say what to change.
70
+
71
+ Reacting to a proposal is faster than writing an answer from a blank page.
72
+
73
+ ### DP11: Agents choose the answer options, guided by you
74
+
75
+ The agent picks the answer options for each card from your guidance. Choices such as yes, yes and, no but, and no are an example, never a default the agent copies. The guidance describes what a good set of options does for you, so agents shape options to each question. Every option and the comment are optional, and you can send an empty answer. The agent gets whatever you sent as structured data.
76
+
77
+ Fixed buttons fit few questions, and guidance that is too precise turns into fixed buttons again.
78
+
79
+ ### DP12: Cards show a priority and come one at a time
80
+
81
+ The inbox shows one card at a time, highest priority first. It comes with default priority levels and definitions, such as decide now, decide soon and can wait. Agents can rename the levels and change their definitions. A level can also be a rule written as a JavaScript expression. The inbox evaluates it in a sandbox, using card properties such as blocked work and wait time, the agent rating, and tags.
82
+
83
+ A long list is hard to work through, and what counts as urgent differs by project.
84
+
85
+ ### DP13: Past answers are searchable, so agents ask once
86
+
87
+ The inbox keeps each card and its answer, and search over them returns in well under a second. An agent searches past answers before it posts, and triage sends back a card that an earlier answer already covers. Each inbox indexes its own cards in its own SQLite store with full-text search: one file on a laptop, and the SQLite storage of its Durable Object in the cloud. Thousands of cards stay a small index. Search by meaning can be added as an extension.
88
+
89
+ The same question from ten sessions should cost you one answer, and a slow search would push agents to ask again.
90
+
91
+ ### DP14: Card guidance is shared, and you approve each broadcast
92
+
93
+ The inbox keeps the guidance agents follow when they write cards: answer options, tone, what context to include. You ask an agent to change it. Agents fetch the current guidance, and a broadcast sends a change to agents that have no open card. Each broadcast first appears to you as a system card. There you pick the agents that receive it, and all agents are picked by default. It goes out only when you approve it.
94
+
95
+ Guidance should change in one place, and nothing should reach all your agents without your approval.
96
+
97
+ ### DP15: Cards group by any property agents provide
98
+
99
+ The queue has no grouping by default. The inbox offers grouping by priority and by agent first. You can also group by any property that agents attach to their cards, such as topic or tag. The set of groupings follows the properties that cards actually carry, and nobody has to declare them in advance.
100
+
101
+ With 100 sessions, one long queue hides which work a set of cards belongs to.
102
+
103
+ ## Delivery
104
+
105
+ ### DP16: Answer delivery depends on the kind of card
106
+
107
+ A card made from a harness blocker, such as an approval prompt, blocks the agent until you answer. A card the agent sends on its own gets an asynchronous answer, inside the current turn or as a new turn. You pick how to deliver it: steer the current turn, queue it as a follow-up, force-send it to interrupt, or let your default apply. Your preference sets the default. The system default is to steer, or to start a new turn when the agent is idle.
108
+
109
+ Most cards are voluntary, so blocking on them would stall the agent for no reason.
110
+
111
+ ### DP17: You see how far your answer got
112
+
113
+ Each answer shows its delivery state: stored, delivered to the harness, or read by the model. An answer queued behind a running tool call shows as delivered but unread. Until the model reads it, the answer waits in an outbox where you can force-send it.
114
+
115
+ You need to know whether the model has your answer yet, and a way to push it through.
116
+
117
+ ### DP18: Each card shows if its agent is listening, busy or offline
118
+
119
+ The card shows one of three states, updated in near real time: listening, busy (connected but working, with unread answers), or offline. You see the state before you start to write. For an offline agent, the card says what will happen to your answer: delivered when the session resumes, or kept in the dead-letter queue. A session that cannot receive channel notifications shows **Channel off** with the reason, in place of listening or busy, because an answer pushed there never reaches the model.
120
+
121
+ A fixed number of seconds cannot be guaranteed.
122
+
123
+ ### DP19: An answer to an offline agent is kept, and agents can find it
124
+
125
+ If the agent goes offline, the inbox stores your answer. It delivers the answer when the session resumes, or to a successor agent you pick. An unclaimed answer goes to a dead-letter queue, which agents can query and claim.
126
+
127
+ Agents crash and sessions end. Your effort should outlast them, and a new session should be able to pick up what an old one missed.
128
+
129
+ ### DP20: Each card has one clear state
130
+
131
+ A card is in one state at a time, shown on the card and in the list. The inbox starts with states such as in triage, open, answered, delivered, read, expired and orphaned. You can ask an agent to add a state, and agents can move their own cards between states.
132
+
133
+ Your workflow will need states nobody listed here, and the agent that posted a card has the facts about where it stands.
134
+
135
+ ### DP21: An agent can retract its card or answer it itself
136
+
137
+ When an agent no longer needs an answer, it retracts the card or closes it with its own answer and a one-line outcome. The card changes state in the inbox at once, and the outcome stays on the card. You can still answer a closed card, and the agent receives your answer. If the agent needs more, it posts a new card that follows the closed one ([DP4](#dp4-a-follow-up-is-a-new-card-linked-both-ways-to-the-one-before)). An answer from an agent is always marked as the agent's, and it is never shown as yours.
138
+
139
+ Agents often find the answer on their own, and you must always see who answered.
140
+
141
+ ### DP22: Cards change in front of you, with a trace
142
+
143
+ When a card changes or closes while you are looking at it, you see what happened and can get back to what was there. The presentation of a change is a design choice that evolves with your feedback, outside the principle.
144
+
145
+ A card that changes without a trace confuses you, and the presentation should be free to change.
146
+
147
+ ### DP23: Answered cards go to an outbox, then to sent
148
+
149
+ When you answer a card, it leaves the queue and goes to the outbox. It moves to the sent list, newest first, once the model has read your answer. You can open a card in either list to read or change your answer.
150
+
151
+ The top of the queue is for what needs you next, and the outbox shows what has not gone through yet.
152
+
153
+ ### DP24: Most cards are short-lived requests, and the conversation stays in the session
154
+
155
+ Most cards ask for one thing and goes away once it is answered. It is never a chat. When an agent needs you again, it sends a new card, and it puts any explanation in the card text. A longer conversation happens in the session, one click away through the card origin. The inbox also handles long cards, such as a rich design spec.
156
+
157
+ Short cards stay quick to answer. The inbox is flexible, but it does not replace documents or artifacts.
158
+
159
+ ## Extensions
160
+
161
+ ### DP25: The core is small, and features are extensions
162
+
163
+ The core handles cards, answers, presence, delivery and templates. Triage, notifications, search, priority scoring and harness integrations are extensions, and the core works when you install none of them. The inbox comes with a built-in web interface, and any other front-end can replace it, such as a native mobile app or a desktop app. A device with a screen and buttons can be a front-end too.
164
+
165
+ A small core runs anywhere, and the front-end is one client of the protocol among others.
166
+
167
+ ### DP26: Extensions attach at named hook points, in process or out
168
+
169
+ Each hook has one contract, whatever the deployment. The extension can be bundled into the build or run as a sidecar on localhost. It can also be an external service over HTTPS or WebSocket. Adding or moving an extension changes configuration, never the core. Each hook declares what a remote extension may do, which can be less than an in-process one. Extensions use their own message schema, separate from the messages agents and cards exchange.
170
+
171
+ The triage filter may run in process on a laptop and as a separate service in the cloud. Remote code deserves fewer powers, and the extension contract should change without touching the agent protocol.
172
+
173
+ ### DP27: Each hook declares its extension failure policy
174
+
175
+ A hook has a timeout and a declared policy: fail-open passes the card on, fail-closed holds it. The card shows the extension that stopped it, with the reason.
176
+
177
+ A slow or broken triage model must not freeze your inbox, or pass poor cards to you unless you chose that.
178
+
179
+ ## Integration
180
+
181
+ ### DP28: Self-hosted by default, from a laptop to the edge
182
+
183
+ The inbox runs as one small process on a laptop, or as a Worker with Durable Objects. Card content stays in the deployment you choose. A public hosted service comes later, on the same code.
184
+
185
+ Cards contain code, plans and secrets from your sessions, and self-hosting comes before any public service.
186
+
187
+ ### DP29: A hosted inbox works without an account and costs almost nothing to run
188
+
189
+ On the hosted service, an inbox is created without a login, and its unguessable URL is the key to it: you bookmark it, and agents pair to it. Each inbox is one Durable Object that hibernates when idle, so an inbox nobody is using costs only its storage.
190
+
191
+ No sign-up keeps onboarding to one step, and near-zero idle cost makes a free public service possible.
192
+
193
+ ### DP30: Usage counts are anonymous and yours to turn on, except on the hosted service
194
+
195
+ Once a day, an inbox can send counts of how much it was used. It counts new cards, answers, pairing requests and page opens, and how many agents, agent processes and tokens connected. Card text, answers, agent names, paths, host names and inbox addresses stay in the inbox.
196
+
197
+ - A laptop inbox sends counts only after you turn them on. Its reports have a random install id, and nothing that says who you are.
198
+ - A hosted inbox always sends counts, with an id made from its invite code, and says so when you create it and when you first open it.
199
+
200
+ The maintainers need to know the product is used, and the hosted service needs to spot a leaked invite code or token. On your own machine, the choice is yours.
201
+
202
+ ## Runs anywhere
203
+
204
+ ### DP31: The core uses web standards only
205
+
206
+ The core uses fetch Request and Response, WebSocket, Web Crypto and JSON. The same core code runs on Node, Bun and Deno on a laptop, on Cloudflare Workers with Durable Objects, in a Chrome extension, and in a standalone web page. Web Crypto makes pairing tokens and signs each call that the core sends to an extension.
207
+
208
+ Web standards are the API that all these hosts share. A browser page can host the inbox for agents that reach it through an extension or a relay, since a page cannot accept connections.
209
+
210
+ ### DP32: All state is in storage, and memory is a cache
211
+
212
+ The inbox writes every card, answer and connection to disk, or to Durable Object storage, before it reports success. It does not keep anything important only in memory. So when a laptop crashes or a Durable Object goes to sleep, the inbox resumes exactly where it was. Timeouts use the platform alarm, which a restart keeps, never a timer inside the process.
213
+
214
+ A laptop process can crash and a Durable Object can hibernate. Both must resume with no lost card.
215
+
216
+ ### DP33: Each inbox has one writer
217
+
218
+ All changes to one inbox go through one place, one at a time. On a laptop that place is one process, and in the cloud it is one Durable Object. Answers saved at the same moment are applied in order, so they cannot overwrite each other, and the code does not need locks.
219
+
220
+ This is how Durable Objects work, and one process on a laptop works the same way. Code that assumes many writers would need locks on both.
221
+
222
+ ### DP34: Presence comes from connections, and it survives hibernation
223
+
224
+ Each agent connection stores its agent id and last-seen time in the socket attachment and in storage, so a hibernated inbox still has the list of connected agents. When a connection misses its heartbeats, the inbox marks the agent offline in near real time.
225
+
226
+ Hibernation clears memory while the sockets remain open. Presence must not depend on memory.
227
+
228
+ ### DP35: Platform details sit behind small adapter interfaces
229
+
230
+ Storage, sockets, alarms, discovery, identity and the Mermaid diagram check are adapters. A build too small to run a check, such as the hosted Worker with its 10 ms of CPU time a request, leaves it to the posting client and the page. The laptop build uses SQLite, a WebSocket server and mDNS. The Cloudflare build uses Durable Object storage, hibernatable WebSockets and alarms.
231
+
232
+ Adding a platform means writing adapters, and nobody changes the core.
233
+
234
+ ### DP36: One documented message model, with pluggable encodings
235
+
236
+ Agents, pages and extensions exchange the same versioned messages. The encoding and the transport are plugins: JSON over WebSocket is the default, with server-sent events and long-poll for clients that cannot keep a socket. A binary encoding such as gRPC with protobuf can be added as a plugin, with no change to the core.
237
+
238
+ One message model keeps every client compatible, and pluggable encodings let fast or constrained clients pick what fits.
239
+
240
+ ### DP37: On a local network, the inbox advertises itself
241
+
242
+ The inbox advertises its address on a LAN with DNS-SD over mDNS, the protocol that Bonjour and Avahi use, under a service type such as _cardinbox._tcp. Its TXT record contains the protocol version and a pairing hint. Advertising is on by default, because a client cannot connect until it pairs, and you can turn it off. A cloud deployment uses a URL instead.
243
+
244
+ Pairing makes the announcement safe: it tells the network only that an inbox exists. Agents on your machines then find it without a URL.
245
+
246
+ ### DP38: Every client proves who it is, even on a LAN
247
+
248
+ Discovery only tells a client where the inbox is. A new agent or device pairs once. Its request appears to you as a system card with a code, and after you confirm it the client gets its own token. You can revoke each token.
249
+
250
+ Anyone on the network can see an mDNS announcement, so finding the inbox must not grant access to it.
251
+
252
+ ### DP39: Agents connect to the inbox from wherever they run
253
+
254
+ Agents run in many places: your shell, containers, cloud sandboxes, CI, and remote machines behind NAT. Agents always dial out to the inbox, so none of them needs an open port. The inbox can also dial out to a remote server over HTTPS or SSH and offer itself there, and a bridge on a machine can offer the inbox to that machine locally or over mDNS. Each agent receives updates over a WebSocket, server-sent events, or long-poll, whichever its environment allows.
255
+
256
+ Agents run in many places, and the inbox should come to them when they cannot come to it.
257
+
258
+ ### DP40: The protocol and card lifecycle are a specification first, and code second
259
+
260
+ The messages, card states and delivery rules are written down as a specification, with a conformance test suite. The reference inbox runs on Linux, macOS and Windows. A separate small build, written in Rust or C, can run a client or a minimal inbox on a microcontroller such as an ESP32.
261
+
262
+ An ESP32 cannot run a JavaScript runtime, so the specification, not one codebase, is what keeps each build compatible.
263
+
264
+ ### DP41: Many links to one inbox act as a mesh
265
+
266
+ An inbox has one stable identity across all its links, whether direct, relayed, tunnelled over SSH, or offered by a bridge. Each message has a unique id, so a message that arrives over two links is delivered once. The inbox and each bridge send over the best working link, and switch when it fails.
267
+
268
+ Several links make the inbox reachable, but they must never show you a card twice or lose an answer.
269
+
270
+ ## Where cards go
271
+
272
+ ### DP42: One inbox for all agents
273
+
274
+ Cards from all sessions, harnesses and machines go to one inbox. You never open a session to find its question.
275
+
276
+ You run about 100 sessions. A view per session does not work at that count.
277
+
278
+ ### DP43: Each card shows where it came from, and one click takes you there
279
+
280
+ A card shows the session name and id, the harness, and where the session runs. That place is a host name, cloud or local, plus the tmux, cmux or herdr pane. Clicking the origin opens that session, wherever it runs. The origin and the link are optional. An agent that cannot be linked still posts cards, and its cards show the facts it has.
281
+
282
+ Sometimes you need the session itself, and some agents run where no link can go.
283
+