@fungi.computer/shiitake 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -2
  3. package/dist/agar-extension.d.ts +9 -0
  4. package/dist/agar-extension.js +38 -0
  5. package/dist/agent-control.d.ts +72 -0
  6. package/dist/agent-control.js +431 -0
  7. package/dist/client-entry.d.ts +211 -0
  8. package/dist/client-entry.js +101 -0
  9. package/dist/client-wire.d.ts +199 -0
  10. package/dist/client-wire.js +271 -0
  11. package/dist/client.d.ts +439 -0
  12. package/dist/client.js +4 -0
  13. package/dist/cloudflare.d.ts +220 -0
  14. package/dist/cloudflare.js +531 -0
  15. package/dist/effect.d.ts +42 -0
  16. package/dist/effect.js +20 -0
  17. package/dist/errors.d.ts +50 -0
  18. package/dist/errors.js +81 -0
  19. package/dist/extension-client.d.ts +38 -0
  20. package/dist/extension-client.js +56 -0
  21. package/dist/extension-contract.d.ts +73 -0
  22. package/dist/extension-contract.js +11 -0
  23. package/dist/extensions.d.ts +589 -0
  24. package/dist/extensions.js +263 -0
  25. package/dist/fleet.d.ts +297 -0
  26. package/dist/fleet.js +515 -0
  27. package/dist/hosted.d.ts +133 -0
  28. package/dist/hosted.js +265 -0
  29. package/dist/http-routes.d.ts +772 -0
  30. package/dist/http-routes.js +242 -0
  31. package/dist/http.d.ts +1 -0
  32. package/dist/http.js +1 -0
  33. package/dist/index.d.ts +35 -0
  34. package/dist/index.js +13 -0
  35. package/dist/internal/accepted-run-executor.d.ts +23 -0
  36. package/dist/internal/accepted-run-executor.js +129 -0
  37. package/dist/internal/admission-door.d.ts +75 -0
  38. package/dist/internal/admission-door.js +105 -0
  39. package/dist/internal/agent-close.d.ts +4 -0
  40. package/dist/internal/agent-close.js +42 -0
  41. package/dist/internal/browser-sessions.d.ts +31 -0
  42. package/dist/internal/browser-sessions.js +173 -0
  43. package/dist/internal/client-boundary.d.ts +262 -0
  44. package/dist/internal/client-boundary.js +202 -0
  45. package/dist/internal/compaction/compaction.d.ts +54 -0
  46. package/dist/internal/compaction/compaction.js +292 -0
  47. package/dist/internal/compaction/session-compaction.d.ts +69 -0
  48. package/dist/internal/compaction/session-compaction.js +330 -0
  49. package/dist/internal/compaction/settings.d.ts +11 -0
  50. package/dist/internal/compaction/settings.js +40 -0
  51. package/dist/internal/compaction/utils.d.ts +45 -0
  52. package/dist/internal/compaction/utils.js +319 -0
  53. package/dist/internal/contracts.d.ts +235 -0
  54. package/dist/internal/contracts.js +1 -0
  55. package/dist/internal/coordinator.d.ts +18 -0
  56. package/dist/internal/coordinator.js +362 -0
  57. package/dist/internal/document-codec.d.ts +11 -0
  58. package/dist/internal/document-codec.js +39 -0
  59. package/dist/internal/effect-client.d.ts +13 -0
  60. package/dist/internal/effect-client.js +102 -0
  61. package/dist/internal/effect-contracts.d.ts +236 -0
  62. package/dist/internal/effect-contracts.js +2 -0
  63. package/dist/internal/extension-command-registry.d.ts +11 -0
  64. package/dist/internal/extension-command-registry.js +29 -0
  65. package/dist/internal/extension-command-tools.d.ts +17 -0
  66. package/dist/internal/extension-command-tools.js +50 -0
  67. package/dist/internal/extension-context.d.ts +25 -0
  68. package/dist/internal/extension-context.js +35 -0
  69. package/dist/internal/extension-definitions.d.ts +5 -0
  70. package/dist/internal/extension-definitions.js +6 -0
  71. package/dist/internal/extension-hook.d.ts +25 -0
  72. package/dist/internal/extension-hook.js +46 -0
  73. package/dist/internal/extension-roster.d.ts +23 -0
  74. package/dist/internal/extension-roster.js +228 -0
  75. package/dist/internal/extension-runtime.d.ts +63 -0
  76. package/dist/internal/extension-runtime.js +1004 -0
  77. package/dist/internal/failure.d.ts +2 -0
  78. package/dist/internal/failure.js +9 -0
  79. package/dist/internal/host-meta.d.ts +41 -0
  80. package/dist/internal/host-meta.js +39 -0
  81. package/dist/internal/json-value.d.ts +4 -0
  82. package/dist/internal/json-value.js +3 -0
  83. package/dist/internal/model-acquisition.d.ts +21 -0
  84. package/dist/internal/model-acquisition.js +98 -0
  85. package/dist/internal/model-preference.d.ts +21 -0
  86. package/dist/internal/model-preference.js +20 -0
  87. package/dist/internal/observation-kernel.d.ts +28 -0
  88. package/dist/internal/observation-kernel.js +169 -0
  89. package/dist/internal/promise-facade.d.ts +18 -0
  90. package/dist/internal/promise-facade.js +172 -0
  91. package/dist/internal/run-scope.d.ts +27 -0
  92. package/dist/internal/run-scope.js +214 -0
  93. package/dist/internal/runtime-coordinator.d.ts +34 -0
  94. package/dist/internal/runtime-coordinator.js +233 -0
  95. package/dist/internal/runtime-core.d.ts +19 -0
  96. package/dist/internal/runtime-core.js +39 -0
  97. package/dist/internal/runtime-reconciliation.d.ts +18 -0
  98. package/dist/internal/runtime-reconciliation.js +162 -0
  99. package/dist/internal/session-context.d.ts +11 -0
  100. package/dist/internal/session-context.js +67 -0
  101. package/dist/internal/session-status.d.ts +10 -0
  102. package/dist/internal/session-status.js +52 -0
  103. package/dist/internal/woodstock/agent-reply.d.ts +25 -0
  104. package/dist/internal/woodstock/agent-reply.js +67 -0
  105. package/dist/internal/woodstock/client-projection.d.ts +29 -0
  106. package/dist/internal/woodstock/client-projection.js +512 -0
  107. package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
  108. package/dist/internal/woodstock/compaction-activity.js +32 -0
  109. package/dist/internal/woodstock/cursor.d.ts +9 -0
  110. package/dist/internal/woodstock/cursor.js +44 -0
  111. package/dist/internal/woodstock/extension-store.d.ts +72 -0
  112. package/dist/internal/woodstock/extension-store.js +262 -0
  113. package/dist/internal/woodstock/fleet-store.d.ts +32 -0
  114. package/dist/internal/woodstock/fleet-store.js +93 -0
  115. package/dist/internal/woodstock/identities.d.ts +6 -0
  116. package/dist/internal/woodstock/identities.js +11 -0
  117. package/dist/internal/woodstock/index.d.ts +170 -0
  118. package/dist/internal/woodstock/index.js +546 -0
  119. package/dist/internal/woodstock/progress.d.ts +32 -0
  120. package/dist/internal/woodstock/progress.js +89 -0
  121. package/dist/internal/woodstock/run-recovery.d.ts +8 -0
  122. package/dist/internal/woodstock/run-recovery.js +95 -0
  123. package/dist/internal/woodstock/session-attention.d.ts +16 -0
  124. package/dist/internal/woodstock/session-attention.js +88 -0
  125. package/dist/internal/woodstock/session-documents.d.ts +18 -0
  126. package/dist/internal/woodstock/session-documents.js +51 -0
  127. package/dist/internal/woodstock/session-entry.d.ts +478 -0
  128. package/dist/internal/woodstock/session-entry.js +484 -0
  129. package/dist/internal/woodstock/session-grants.d.ts +24 -0
  130. package/dist/internal/woodstock/session-grants.js +84 -0
  131. package/dist/internal/woodstock/session-inbox.d.ts +56 -0
  132. package/dist/internal/woodstock/session-inbox.js +307 -0
  133. package/dist/internal/woodstock/session-store.d.ts +432 -0
  134. package/dist/internal/woodstock/session-store.js +2006 -0
  135. package/dist/internal/woodstock/storage-admission.d.ts +14 -0
  136. package/dist/internal/woodstock/storage-admission.js +44 -0
  137. package/dist/model-preference.d.ts +20 -0
  138. package/dist/model-preference.js +30 -0
  139. package/dist/node.d.ts +64 -0
  140. package/dist/node.js +256 -0
  141. package/dist/remote-client.d.ts +11 -0
  142. package/dist/remote-client.js +298 -0
  143. package/dist/session-attention.d.ts +35 -0
  144. package/dist/session-attention.js +22 -0
  145. package/dist/session-documents.d.ts +28 -0
  146. package/dist/session-documents.js +1 -0
  147. package/dist/testing.d.ts +54 -0
  148. package/dist/testing.js +215 -0
  149. package/docs/api/README.md +26 -0
  150. package/docs/api/agar-extension.md +40 -0
  151. package/docs/api/agent-control.md +151 -0
  152. package/docs/api/client-entry.md +344 -0
  153. package/docs/api/client.md +1112 -0
  154. package/docs/api/cloudflare.md +500 -0
  155. package/docs/api/effect.md +904 -0
  156. package/docs/api/extensions.md +2003 -0
  157. package/docs/api/fleet.md +706 -0
  158. package/docs/api/hosted.md +382 -0
  159. package/docs/api/http.md +111 -0
  160. package/docs/api/index.md +1116 -0
  161. package/docs/api/model-preference.md +151 -0
  162. package/docs/api/node.md +142 -0
  163. package/docs/api/testing.md +250 -0
  164. package/package.json +130 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jamstack Consulting LLC
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/README.md CHANGED
@@ -1,3 +1,455 @@
1
- # Temporary Holding Version
1
+ # Shiitake (`@fungi.computer/shiitake`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shiitake runs durable Agent conversations. A Session holds accepted work,
4
+ messages and observations, while the host supplies storage, models, tools and
5
+ the wake that runs queued work.
6
+
7
+ There are three ways to run it:
8
+
9
+ - **In your code**: install this package and host Agents yourself, on Node for
10
+ tests or in a
11
+ [Cloudflare Durable Object](https://shiitake.shiit.app/run/cloudflare/).
12
+ - **On your machine**: the [`fungi` CLI](https://docs.fungi.computer/install/)
13
+ is a Shiitake Agent with a terminal interface.
14
+ - **Hosted**: [Fungi](https://shiitake.shiit.app/run/fungi/) runs Agents for
15
+ your Team, and its models work from your own host too.
16
+
17
+ To add your own behaviour, start the extension guide at
18
+ [Your first extension](https://shiitake.shiit.app/extensions/first-extension/).
19
+
20
+ ## Set up
21
+
22
+ ```sh
23
+ npm install @fungi.computer/shiitake
24
+ ```
25
+
26
+ The package ships ESM only and depends on Effect, Zod, and Hono, plus the Fungi
27
+ packages [Cairn](../cairn/README.md), [Mule](../mule/README.md),
28
+ [Mycelium](../mycelium/README.md), [Watchdog](../watchdog/README.md),
29
+ [Bird Dog](../bird-dog/README.md) and Knapsack, all installed automatically.
30
+
31
+ `Shiitake.make(options)` is the Promise constructor. The Effect 4 constructor is
32
+ `make` from `@fungi.computer/shiitake/effect`. Both use the same owner. The host
33
+ supplies:
34
+
35
+ | Input | Responsibility |
36
+ | -------------- | ------------------------------------------------------------------------------------------------------ |
37
+ | `store` | A native SQLite owner with scalar rows and synchronous transactions. |
38
+ | `runtime` | Bind reconciliation, commit mutations with a wake guarantee, schedule work and close the registration. |
39
+ | `model` | Acquire a Pi model and its stream, then release them. The host owns selection and credentials. |
40
+ | `instructions` | Load an instruction snapshot and revision for the run. |
41
+ | `extensions` | An explicit roster and capability grants. Use an empty roster and grants when none are needed. |
42
+ | `tools` | Optional Mycelium modules, sources, the host's sandbox and `access` grants passed down as data. |
43
+ | `meta` | Optional schema for the opaque metadata the host attaches to prompts and operator lines. |
44
+
45
+ Construction needs an owner and a runtime host that meet their transaction and
46
+ wake contracts. Existing host fixtures show deterministic and native adapters.
47
+
48
+ ## Quick start without a provider account
49
+
50
+ Node 24 or newer provides the SQLite database for the public testing helper.
51
+ This runs the real durable Agent with a deterministic faux model; no credentials
52
+ or background timer are needed. Drive settlement explicitly.
53
+
54
+ ```ts
55
+ import { memoryHost } from "@fungi.computer/shiitake/testing";
56
+
57
+ const host = await memoryHost();
58
+ try {
59
+ const receipt = await host.agent.session("example-session").send("Hello", {
60
+ requestId: "example-first-prompt",
61
+ });
62
+ const snapshot = await host.settle("example-session");
63
+ console.log(receipt.status, snapshot.terminals.at(-1)?.outcome);
64
+ } finally {
65
+ await host.close();
66
+ }
67
+ ```
68
+
69
+ `memoryHost({ owner, model, extensions, tools })` accepts canonical inputs and
70
+ borrows a supplied owner without closing its database. `extensions` may be a
71
+ plain list; the test host then grants each extension exactly what it requests
72
+ (`Shiitake.make` always takes explicit grants). `script` replaces `model` and
73
+ `tools`: strings are replies, `callTool("bookmarks.add", input)` is a tool call
74
+ that a scripted sandbox runs through the admitted operation, and
75
+ `echoSystemPrompt()` replies with the system prompt the model received.
76
+ `fauxModelSource(replies)` supplies deterministic responses for each model
77
+ acquisition. `reconcile()` drives one canonical tick;
78
+ `settle(sessionId, maxTicks)` accepts a 1–1,000 tick budget (default 1,000) and
79
+ refuses when it is exhausted. This manually driven helper does not assert host
80
+ wake delivery; the native runtime-law tests qualify that scheduling contract.
81
+ The helper is **not isolation**: trusted callbacks retain the process's
82
+ privileges, and submitted JavaScript is never evaluated.
83
+
84
+ Production callers use `Shiitake.make(options)` with their storage and wake
85
+ owner. Keep that host open while it owns active work. Closing it stops runtime
86
+ resources; it does not erase SQLite data or turn acceptance into a completed
87
+ reply.
88
+
89
+ ## Accept, execute and observe
90
+
91
+ An accepted prompt returns `status: "accepted"` and an owner-issued `commandId`
92
+ after the durable commit. Repeating the same request identity and input replays
93
+ the receipt. Changing the input under that identity is a conflict.
94
+
95
+ The runtime wakes reconciliation, acquires run resources, executes the model and
96
+ tools, releases resources and records settlement. The host owns provider
97
+ configuration and retry policy. Shiitake owns durable Session facts and work.
98
+
99
+ `session.read()` returns a bounded baseline. `session.watch()` owns a baseline
100
+ and live changes resource. Close the watch when its consumer leaves. Addressing
101
+ a Session performs no I/O. A client uses `createShiitakeClient` from the
102
+ `/client` entry with the host's authorized transport. Releasing that client does
103
+ not cancel host work.
104
+
105
+ Cancellation targets an accepted `commandId`. `too_late` is an ordinary outcome
106
+ when that work can no longer be withdrawn. `session.abort()` atomically requests
107
+ cancellation of the running turn and every queued prompt in that Session.
108
+ Cancelled prompts retain their text for resubmission. Neither a cancelled
109
+ observation nor a closed browser proves a tool has stopped.
110
+
111
+ ## Extensions and authority
112
+
113
+ Use `defineExtension("id")` from `/extensions`. The version defaults to `0.0.0`;
114
+ pass `{ id, version }` when a behaviour change is not visible in the schemas,
115
+ config or requested capabilities (for example a fixed hook). The version joins
116
+ those in the extension's revision; a recorded stop persists until the revision
117
+ changes or the host resumes the extension. Configuration, collections and
118
+ documents are declared before callbacks.
119
+ `.state({ name: defineCollection(...) })` declares state version 1; pass
120
+ `{ version, collections, migrations }` when a row schema changes. The host's
121
+ grants are separate from the definition's requests. Product feature names belong
122
+ to the host roster.
123
+
124
+ `context.state.read((s) => ...)` reads rows synchronously outside a transaction,
125
+ for example in a prompt section; `context.state.update` writes. A projection's
126
+ `read` returns the value itself (or an `Option`; `undefined` publishes none).
127
+ The `tools` builder's third argument exposes a declared command to the model:
128
+ `commands.tool("add", { description })` is a Mycelium operation that runs the
129
+ same handler in the same transaction, scoped to the run's Session.
130
+
131
+ Commands, settled hooks and timer hooks run synchronously within the owner
132
+ transaction. Prompt and tool builders run outside that write transaction.
133
+ Activation resources use the Effect scope. An extension failure records a stop
134
+ reason. Resuming it replays missed settled work through its cursor.
135
+
136
+ `context.send({ sessionId, requireExistingSession: true, ... })` opts into an
137
+ existing target: the admission transaction rejects an absent Session with
138
+ `AdmissionError` reason `closed`, without creating it. Leave the condition out
139
+ when creating the addressed Session is intended. Automated continuations use the
140
+ condition to prevent recreating a Session after deletion.
141
+
142
+ An extension declaring `attention: true` can request the `attention` grant.
143
+ `context.attention.set(sessionId, item)` and `clear(sessionId, id)` share its
144
+ owner transaction and publish after commit. Items have `id`, `kind`, `summary`
145
+ (at most 200 characters), `since` and optional `expiresAt`; each Session carries
146
+ at most 64. An owner may retain at most 4,096 outstanding items across Sessions;
147
+ clearing releases capacity. Public observations qualify ids with `extensionId`.
148
+ Open attention makes the inbox `needs_you`, ahead of running or settled work.
149
+ Stopped owners are excluded until resumed, and owners without a current grant
150
+ are excluded until the grant returns. Expiry is display metadata; the
151
+ extension's timer owns clearing, and restarting does not discard pending
152
+ attention. Clearing an absent item, including after Session deletion, is
153
+ idempotent; setting an item requires an existing Session.
154
+
155
+ Clients bind an extension contract with `bindSessionExtension` or
156
+ `bindAgentExtension` from `/client`. Projection slots describe observed state,
157
+ not assumed completion. See the
158
+ [prompt/tool test](test/extension-prompt-sections.test.ts),
159
+ [state binding test](test/extension-state-binding.test.ts) and
160
+ [close test](test/c2-extension-close-orchestrator.test.ts).
161
+
162
+ An extension may request the exclusive `files` grant and return Knapsack-minted
163
+ `FileTool`s from `files(context, run)`. Core captures the tools once per run,
164
+ passes them to Mycelium beside `execute`, and records their names in the
165
+ execution protocol digest. A second granted holder is refused. This contribution
166
+ adds no shared Files module to `execute`; explicit Computer capabilities remain
167
+ independent.
168
+
169
+ Shiitake holds no permission logic. Tool authors label each operation `read` or
170
+ `write` (and optionally `confirm`); the host owns every grant decision and
171
+ delivers it down as versioned data, and Mycelium decides each call.
172
+ `tools.access.grants` is the Agent-wide default. The in-process owner returned
173
+ by `Shiitake.make` delivers one Session's grants with
174
+ `agent.grants.deliver(sessionId, { version, grants })`, synchronously; code
175
+ inside a Cloudflare owner also calls `deliverSessionGrantsToStorage` while its
176
+ runtime is cold. Versions never move back: an older version is refused, and the
177
+ same version with other grants replaces the stored ones, so a host that
178
+ recomputes its decision never leaves the old grant in place. Delivered grants
179
+ override the default key by key, and Mycelium reads them at every call, so a
180
+ change applies to the very next call, including in queued, recovered and
181
+ extension-started runs. Grants are keyed by module id; a module an extension
182
+ built is granted by its roster grant's `grantKey` (the extension id by default),
183
+ and Files direct tools by `files`. Keys listed in `access.required` fail closed:
184
+ a Session with no delivered or default grant for them is refused. With no access
185
+ data at all, every tool keeps full access, so a first-time host writes no
186
+ permission code; a `confirm` operation is still refused unless the host supplies
187
+ `access.confirm`. A Files extension still requires a host tool runtime.
188
+ Extension tool authority and final external access remain host decisions.
189
+
190
+ ## Host metadata
191
+
192
+ A host attaches its own data to a prompt or operator line, such as who sent it
193
+ or which channel it came from, with `agent.withMeta(meta)`. Shiitake checks the
194
+ value against the host's `meta.schema`, caps it at 4096 bytes of JSON, stores it
195
+ beside the message and returns it as `SessionMessage.meta`. It never reads it.
196
+ Only the in-process owner returned by `Shiitake.make` has `withMeta`: no request
197
+ body can carry metadata, because every command schema is closed. Over HTTP the
198
+ host's own middleware sets the `shiitakeMeta` Hono variable, and the session
199
+ resolver receives it:
200
+
201
+ ```ts
202
+ const routes = createShiitakeHttpRoutes(
203
+ (id, meta) => (meta === undefined ? agent : agent.withMeta(meta)).session(id),
204
+ agent.listSessions,
205
+ agent.createSession,
206
+ agent.deleteSession,
207
+ () => agent.extensions,
208
+ );
209
+ const app = new Hono<ShiitakeHttpEnv>()
210
+ .use(async (c, next) => {
211
+ c.set("shiitakeMeta", await authenticatedMeta(c.req.raw));
212
+ await next();
213
+ })
214
+ .route("/", routes);
215
+ ```
216
+
217
+ Every accepted command takes the same path: a prompt, a steer, an operator line,
218
+ a cancel and an abort each store their metadata on the entry they write, so a
219
+ restart loses none of it. A cancelled run's `SessionTerminal.meta` is its
220
+ cancellation's metadata. Addressed mail work stores the metadata its host passes
221
+ to `ShiitakeHost.acceptMail(mail, meta)`; a mail's sender cannot set it, and a
222
+ reply never carries the settled work's metadata (see
223
+ [Delegated replies](#delegated-replies)). A retried request id replays only with
224
+ the same metadata. A Cloudflare owner exposes its whole HTTP surface (Session
225
+ routes, browser and ACP) as the protected `shiitakeApp()` Hono app, so a host
226
+ mounts it behind its own identity middleware; ACP receives each request's
227
+ metadata, so a host can serve each author over `withMeta`. The host's metadata
228
+ schema may be an Effect schema or any synchronous Standard Schema, such as
229
+ Zod 4. [Host metadata tests](test/host-meta.test.ts) cover each rule.
230
+
231
+ Cloudflare hosts may supply `initialize(extensions)` to commit an ordinary
232
+ Agent-scope extension command before queued or host work resumes. Failure closes
233
+ the incomplete runtime and leaves its durable work for a later open.
234
+
235
+ Cloudflare hosts may configure `hostJobs` to open a concurrent Watchdog lane.
236
+ Their `hostWork` pass stages and recovers delivery, then calls that lane's
237
+ `tickConcurrent`. Every wake requests Agent reconciliation and host work
238
+ independently; both share one durable alarm. During a live tool, its armed
239
+ five-second heartbeat requests delivery processing after an attention write.
240
+ Receiver duration and ordinary retry policy still apply. A failed pass retains
241
+ its retry until that pass succeeds, and close joins both active passes.
242
+
243
+ ## Run it on Node
244
+
245
+ `@fungi.computer/shiitake/node` keeps one durable Agent in a SQLite file on
246
+ disk, on the Node event loop. `openNodeShiitake({ path, ... })` takes the same
247
+ model, instructions, extensions and tools as `Shiitake.make`, holds an exclusive
248
+ lease on the file until `close`, and returns the in-process `agent` plus its
249
+ Session `routes` as a Hono app to serve behind your own middleware. Reopening
250
+ the file resumes accepted work. `nodeRuntimeHost` and `acquireNodeStoreLease`
251
+ are the same pieces for a host that composes its own store. See
252
+ [Run it on Node](docs/site/run/node.mdx) and the
253
+ [Node host tests](test/node-host.test.ts).
254
+
255
+ The `/agent-control` source exposes `agents.create({requestId, model?})`,
256
+ `agents.setModel({agentId, model})` and `agents.models({})` when its host
257
+ authorizes those operations. Models use `provider/model` spelling and must be
258
+ offered by that host; `agents.models` returns at most 200 offered addresses from
259
+ signed-in or configured providers. A host model refusal reaches the caller as a
260
+ `CapabilityFailed` diagnostic such as `model:rejected: …`, while other failures
261
+ propagate unchanged. The `/model-preference` resolver orders explicit Session,
262
+ explicit Agent, the person's last manual switch, Team default, then available
263
+ login. Hosts own those stored layers and credential admission: choosing a model
264
+ never bypasses an owner's Team or Agent key-source restriction. A host can
265
+ refuse an unrunnable send before acceptance; `agents.wait` includes the failed
266
+ run's bounded diagnostic so callers can explain login or model-selection
267
+ problems. For a completed command, `agents.wait` also returns the command's
268
+ final assistant `text`, at most 16,384 characters, with `truncated` set when it
269
+ was cut; failed, cancelled and interrupted commands return no text, and
270
+ `agents.read` stays the full bounded Session read. Hosted ACP sessions use the
271
+ Agent and Team layers; they do not select a person's saved model preference.
272
+
273
+ ## API and examples
274
+
275
+ The [generated API reference](docs/api/README.md) covers the public exports in
276
+ [package.json](package.json):
277
+
278
+ | Entry | Use |
279
+ | ------------------------------------- | -------------------------------------------------------------------------------- |
280
+ | Root and `/effect` | Promise and Effect host construction. |
281
+ | `/client`, `/http` | Neutral Session clients and HTTP route composition. The host authorizes ingress. |
282
+ | `/cloudflare` | Durable Object storage and alarm composition, including owner-local handles. |
283
+ | `/extensions`, `/client-entry` | Host authoring and the independent `shiitake.client/v1` presentation contract. |
284
+ | `/agent-control`, `/model-preference` | Authorized Agent operations and model selection contracts. |
285
+
286
+ [Bound transport tests](test/bound-fetch-client.test.ts) exercise a client with
287
+ a host-supplied fetch.
288
+ [Restart tests](test/resume-after-restart-tool-outcomes.test.ts) exercise
289
+ interrupted tools against the existing replay rules. They are local proofs, not
290
+ a guarantee about another deployment's storage or sandbox.
291
+
292
+ ## Client entries
293
+
294
+ A host selects client entries separately from its durable extension roster.
295
+ `defineClientEntry(contract, callbacks)` captures the projection decoder for the
296
+ extension. The installed entry has only `protocol`, `id`, optional `attach`, and
297
+ optional `contribute`. At least one callback is required. The host admits
298
+ exactly `shiitake.client/v1`; it does not accept the superseded client shape.
299
+
300
+ `contribute` is a synchronous, pure function of the extension's typed view. It
301
+ returns display-only chips, pinned panel rows, committed timeline cells, and an
302
+ optional waiting summary/cancel action. It has no renderer nodes, subscriptions,
303
+ Session transport, or action closures. Parakeet evaluates changed slots once
304
+ outside rendering and decodes the result at that seam. Invalid output retires
305
+ that entry's rows and reports its error without disabling other entries.
306
+
307
+ This illustrative author uses the same factory exercised by
308
+ [test/client-entry.test.ts](test/client-entry.test.ts):
309
+
310
+ ```ts
311
+ import { ExtensionId } from "@fungi.computer/shiitake/extensions";
312
+ import { defineClientEntry } from "@fungi.computer/shiitake/client-entry";
313
+ import * as Schema from "effect/Schema";
314
+
315
+ const contract = {
316
+ id: ExtensionId.make("progress"),
317
+ commands: {},
318
+ projection: { schema: Schema.Struct({ title: Schema.String }) },
319
+ };
320
+ export default defineClientEntry(contract, {
321
+ contribute: (view) => ({
322
+ chips: [],
323
+ panel:
324
+ view.status === "running" && view.value !== null
325
+ ? [{ id: "progress", title: view.value.title }]
326
+ : [],
327
+ timeline: { cells: [], omitted: 0 },
328
+ waiting: null,
329
+ }),
330
+ });
331
+ ```
332
+
333
+ Attachment authors may return `void`, a Promise, or a scoped Effect. The context
334
+ supplies `activity`, `signal`, and `onError`; it supplies no Session authority.
335
+ Each `for await` over activity creates an independent subscription with one
336
+ sliding latest-value slot. A slow consumer sees the latest value. The selected
337
+ Session's detach aborts the signal and ends every iterator, discarding buffered
338
+ activity. Ordinary async authors use that signal for their own resource cleanup.
339
+
340
+ Actions are `{ id, label, input? }` data from `@fungi.computer/gills`. Their IDs
341
+ must occur in the same entry's running advertised Whistle catalog, and input
342
+ must be a JSON object. Hosts execute the catalog command with its existing
343
+ prompts and notices; there is no second action registry. Offline and stale
344
+ actions refuse locally. Waiting cancellation preserves notices but rejects
345
+ prompts without opening a dialog. Browser rows use ordinary buttons. The TUI
346
+ renders current extension rows compactly below its footer hints; each row moves
347
+ the footer upward. Loop and its next run share one row. Ordinary notices can be
348
+ dismissed with Esc after interactions, queued messages, active work and
349
+ extension waiting controls have priority. Live extension state is not
350
+ dismissible. In the TUI, the **Row actions** palette command uses the existing
351
+ keyboard choice dialogs. Rows may also carry a typed Session open target. The
352
+ host supplies the generic open callback and authorizes that target before
353
+ navigation. The TUI opens the target through its pane registry and reports a
354
+ refused open as a notice.
355
+
356
+ V1 permits four chips, sixteen panel rows, thirty-two timeline cells, and two
357
+ hundred rows total per entry. Labels contain at most 4,096 characters. Chips
358
+ carry no actions or open targets. `ClientChipV1.compact` is optional label data
359
+ that browser and TUI renderers do not display; they use the chip row’s title. A
360
+ cell's ID is its positive safe integer sequence written as decimal text.
361
+ Producer history commits in the same owner transaction as its state change,
362
+ deduplicates unchanged state, and retains at most thirty-two events and 32 KiB
363
+ with an explicit omitted count. A real command ID anchors cells after its last
364
+ loaded durable row; an unloaded anchor stays hidden until its page loads. Null
365
+ anchors appear under **Extension changes**.
366
+
367
+ The frozen client V1 contract includes fields, placements, bounds, identities,
368
+ ordering, admission, dispatch, detach, and history laws. Host extension V0 and
369
+ client presentation V1 are independent protocols. A breaking client change
370
+ requires a new client protocol; supported additions require an explicit contract
371
+ decision and proof. Producer schema changes bump their own extension/state
372
+ versions. Pre-launch data uses the current contract, without a dual reader.
373
+
374
+ ## Limits and errors
375
+
376
+ Use `ShiitakeError`'s boundary and cause to distinguish invalid input, conflict,
377
+ storage failure and unavailable execution. Preserve request identity when an
378
+ acknowledgement is uncertain. Do not manufacture an accepted result from an
379
+ unavailable owner.
380
+
381
+ Safe recovery depends on durable facts and the tool's replay contract. An
382
+ operation with an uncertain external effect is not made safe by retrying it.
383
+ Schema-incompatible extension state fails rather than using another shape.
384
+ Credentials, product permissions, browser delivery and deployment remain with
385
+ the host.
386
+
387
+ ## Contribute and license
388
+
389
+ Add a regression to the nearest Session, extension or host test. From the root:
390
+
391
+ ```sh
392
+ pnpm --dir packages/shiitake run test:unit
393
+ pnpm --dir packages/shiitake run test:merge
394
+ pnpm --dir packages/shiitake run docs
395
+ ```
396
+
397
+ The native merge proof needs the configured Workers-compatible runtime. Unit
398
+ tests alone do not qualify that host. MIT. See [LICENSE](LICENSE).
399
+
400
+ ## Agent fleet
401
+
402
+ `@fungi.computer/shiitake/fleet` owns Agent directory reservations, replay
403
+ receipts, erasure addresses, granted Session operations and hosted BirdDog
404
+ delegation. Hosts supply grants containing Agent scope, actions, expiry and
405
+ provenance. Fleet operations do not read Team, billing or broker policy. Usage
406
+ has no fleet cap; the host's inference admission remains the credit authority.
407
+
408
+ `createAgentDirectory(sql)` runs mutations inside the caller's transaction and
409
+ returns lifecycle facts so host projections commit with the directory change.
410
+ `createFleetSessions(open)` lists, creates, reads and deletes Sessions, and
411
+ `send(grant, agentId, sessionId, message)` admits replayable work. An omitted
412
+ Session is created with the message's request identity.
413
+ `createFleetDelegation({storage, deliver, open, readSettlement})` records the
414
+ grant's parent Session and freezes BirdDog mail before delivery.
415
+ `start(grant, task)` returns the admission receipt; `delegate(grant, task)`
416
+ returns Agar Pending until the exact command settles, then its terminal, final
417
+ text, native usage facts and tool-call count. The host-only
418
+ `readSettlement(sessionId, commandId)` reads exact work rows, independently of
419
+ transcript pagination and branch selection. Poll with the same request identity
420
+ and task. Closing a client never cancels work.
421
+
422
+ Host metadata stays opaque. An in-process `open` adapter applies it with
423
+ `agent.withMeta(meta)`; a remote host attaches it at its authenticated transport
424
+ edge. Cancellation returns the cancellation entry's metadata, allowing the host
425
+ to name the canceller in the reply text without making them its author.
426
+
427
+ ### Delegated replies
428
+
429
+ A `followUp` delegation returns `{status: "accepted", commandId}` at once. The
430
+ child's result arrives later as one new user-role line in the parent Session. It
431
+ is ordinary follow-up work: an idle parent wakes a new run, and an active parent
432
+ queues it behind the running work, which it never interrupts, so it runs next.
433
+ Each fact on that line has one setter:
434
+
435
+ | Fact | Setter | Rule |
436
+ | ----------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
437
+ | Content | BirdDog `settlementReplies` | The child's last assistant text, or the host's cancellation text such as `Work cancelled by Ada.`, verbatim; with neither, one sentence naming the outcome. |
438
+ | `reply.commandId` | BirdDog, from the child's accepted command | Equals the parent's `agents.send` receipt `commandId`. |
439
+ | `reply.outcome`, `reply.diagnostic` | BirdDog, from the exact Watchdog outcome and the child Session's terminal diagnostic | `completed`, `failed`, `interrupted`, `cancelled` and `outcome_unknown` stay distinct. Only `failed`, `interrupted` and `outcome_unknown` carry a diagnostic, bounded by `boundedDiagnostic`. |
440
+ | Authentication | `fleet.authenticateReply(mail)` at the parent's host | The key is the frozen send's `<key>:reply:0`, `replyTo` is empty, `from`/`to` mirror the frozen send exactly, BirdDog's facts are present and, once the send's receipt is recorded, name its `commandId`. Otherwise the result is `undefined` and the host refuses the mail. |
441
+ | Author and `meta.delegation.reply` | The parent's host, only after authentication | The author is the replying child Agent. The settling actor, a human canceller and the parent never author a reply. |
442
+ | Model-visible provenance | Shiitake, from the `reply` the host passes to `acceptMail(mail, meta, reply)` | The model sees the line inside `<fungi_agent_reply agent_id=… session_id=… command_id=… outcome=… [diagnostic=…]>`. |
443
+
444
+ The provider role stays `user`. Shiitake renders the tag from the verified
445
+ facts, never from the text, and neutralizes the tag in every other user-role
446
+ line (typed prompts, unverified mail, operator lines) and inside the reply body.
447
+ Typed or mailed text that copies the tag therefore never reads as a verified
448
+ reply. The stored line keeps the child's text verbatim; clients join it to the
449
+ send on `meta.delegation.reply.commandId` through agar's
450
+ `messageDelegationReply`, never on its text. `acceptMail` refuses a `reply` that
451
+ does not name its mail's sender or that arrives on mail with a `replyTo`.
452
+
453
+ Apps exposing an Agent extension import `agentExtensionMethods` from
454
+ `@fungi.computer/shiitake/agar-extension` and spread the result into Agar's
455
+ `defineAppBackend({methods})`. Agar itself has no Shiitake dependency.
@@ -0,0 +1,9 @@
1
+ import type { RosteredExtensionV0 } from "./extensions.js";
2
+ import { type AppBackendMethod } from "@fungi.computer/agar/backend-definition";
3
+ /**
4
+ * Serve one Shiitake extension from this App backend. Spread the result into
5
+ * `defineAppBackend({ methods })`; the build writes the three methods into the
6
+ * Release's API, which is how a Release declares itself an Agent extension.
7
+ * An extension requesting a capability hosting lacks fails here, at build.
8
+ */
9
+ export declare function agentExtensionMethods<Context = void>(extension: RosteredExtensionV0): Readonly<Record<string, AppBackendMethod<Context>>>;
@@ -0,0 +1,38 @@
1
+ import { agentExtensionMethodNames, hostAgentExtension, hostedCallInputSchema, hostedCallOutputSchema, hostedPromptInputSchema, hostedPromptOutputSchema, hostedToolsInputSchema, hostedToolsOutputSchema, } from "./hosted.js";
2
+ import { defineAppMethod, } from "@fungi.computer/agar/backend-definition";
3
+ /**
4
+ * Serve one Shiitake extension from this App backend. Spread the result into
5
+ * `defineAppBackend({ methods })`; the build writes the three methods into the
6
+ * Release's API, which is how a Release declares itself an Agent extension.
7
+ * An extension requesting a capability hosting lacks fails here, at build.
8
+ */
9
+ export function agentExtensionMethods(extension) {
10
+ const hosted = hostAgentExtension(extension);
11
+ const id = hosted.manifest.id;
12
+ return Object.freeze({
13
+ [agentExtensionMethodNames.prompt]: defineAppMethod({
14
+ description: `Prompt sections of the ${id} Agent extension.`,
15
+ effect: "read",
16
+ requires: [],
17
+ input: hostedPromptInputSchema,
18
+ output: hostedPromptOutputSchema,
19
+ handler: (input) => hosted.prompt(input),
20
+ }),
21
+ [agentExtensionMethodNames.tools]: defineAppMethod({
22
+ description: `Tools of the ${id} Agent extension for one run.`,
23
+ effect: "read",
24
+ requires: [],
25
+ input: hostedToolsInputSchema,
26
+ output: hostedToolsOutputSchema,
27
+ handler: (input) => hosted.tools(input),
28
+ }),
29
+ [agentExtensionMethodNames.call]: defineAppMethod({
30
+ description: `Run one tool of the ${id} Agent extension.`,
31
+ effect: "write",
32
+ requires: [],
33
+ input: hostedCallInputSchema,
34
+ output: hostedCallOutputSchema,
35
+ handler: (input) => hosted.call(input),
36
+ }),
37
+ });
38
+ }
@@ -0,0 +1,72 @@
1
+ import { type CapabilitySource, type CapabilitySession } from "@fungi.computer/mycelium";
2
+ import type { Accepted, ShiitakeAgent, ShiitakeSession } from "./client.js";
3
+ import { type ModelPreference } from "./model-preference.js";
4
+ /** One existing Agent visible within the caller's authorized project or Team. */
5
+ export type AgentControlAgent = Readonly<{
6
+ agentId: string;
7
+ name: string;
8
+ }>;
9
+ /** One host-offered model usable as an Agent's create or setModel choice. */
10
+ export type AgentControlModel = Readonly<{
11
+ /** `provider/model` address accepted by `agents.create` and `agents.setModel`. */
12
+ address: string;
13
+ /** Provider's display name for the model. */
14
+ name: string;
15
+ }>;
16
+ /** One host-authorized delegation, with the caller Session supplied by acquisition. */
17
+ export type AgentDelegationInput = Readonly<{
18
+ /** Target Agent in the host's authorized scope. */
19
+ agentId: string;
20
+ /** Target conversation on that Agent. */
21
+ sessionId: string;
22
+ /** Work to admit as a follow-up prompt. */
23
+ text: string;
24
+ }>;
25
+ /** Host-only identity and admitted command metadata for one delegated send. */
26
+ export type AgentDelegationInvocation = Readonly<{
27
+ toolCallId: string;
28
+ sendIndex: number;
29
+ commandId?: string;
30
+ meta?: CapabilitySession["meta"];
31
+ toolCallCode?: string;
32
+ }>;
33
+ /** Host-owned discovery and per-operation access to ordinary Agents. */
34
+ export type AgentControlDirectory = Readonly<{
35
+ /** Return Agents authorized by the host's current project or Team scope. */
36
+ list: (signal: AbortSignal) => Promise<readonly AgentControlAgent[]>;
37
+ /** Follow-up delegation with durable replies to the invoking Session. */
38
+ send?: (input: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation) => Promise<Accepted>;
39
+ /** Present only when this host authorizes ordinary Agent creation. */
40
+ create?: (input: Readonly<{
41
+ requestId: string;
42
+ model?: ModelPreference;
43
+ }>, signal: AbortSignal) => Promise<AgentControlAgent>;
44
+ /** Present only when the host authorizes changing an Agent's future default. */
45
+ setModel?: (input: Readonly<{
46
+ agentId: string;
47
+ model: ModelPreference;
48
+ }>, signal: AbortSignal) => Promise<void>;
49
+ /** Present only when the host can list models offered by its signed-in providers. */
50
+ models?: (signal: AbortSignal) => Promise<readonly AgentControlModel[]>;
51
+ /** Optional host admission check before accepting a model-backed send. */
52
+ checkModel?: (input: Readonly<{
53
+ agentId: string;
54
+ sessionId: string;
55
+ }>, signal: AbortSignal) => Promise<void>;
56
+ /** Recheck authorization and open one addressed Agent for this operation. */
57
+ open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
58
+ agent: ShiitakeAgent;
59
+ /** Release operation access without stopping the target Agent. */
60
+ release: () => Promise<void>;
61
+ }>>;
62
+ }>;
63
+ /** Observe one durable terminal; timeout or abort closes observation, not the work. */
64
+ export declare function waitForAgentTerminal(session: ShiitakeSession, commandId: string, timeoutMs: number, signal: AbortSignal, startedAt?: number): Promise<Readonly<{
65
+ commandId: string;
66
+ outcome: "completed" | "cancelled" | "failed" | "interrupted";
67
+ sequence: number;
68
+ diagnostic: string | null;
69
+ meta?: import("./extensions.js").Json;
70
+ }>>;
71
+ /** Compose one authorized Agent-control projection for a single run lease. */
72
+ export declare function createAgentControlSource(directory: AgentControlDirectory): CapabilitySource;