capsule-runtime 0.11.0

The runtime that executes Murmur capsules. Runs packaged AI agents in isolation, enforcing WASI capability grants on a Wasmtime component sandbox.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# WIT package versioning policy

Every `murmur:*` WIT package under `crates/capsule-runtime/wit/**` carries an
explicit `@x.y.z` version on its `package` declaration. The version is embedded
into the component-type section of any `.wasm` compiled against it, so the
interface contract an artifact was built against is readable from the binary
itself rather than inferred by probing which functions happen to be exported.

Current versions:

| Package                  | Version  |
| ------------------------ | -------- |
| `murmur:hook`            | `0.9.0`  |
| `murmur:tool`            | `0.2.0`  |
| `murmur:capsule`         | `0.1.0`  |
| `murmur:tool-registry`   | `0.2.0`  |
| `murmur:artifact-manager`| `0.1.0`  |
| `murmur:shell`           | `0.1.0`  |
| `murmur:message`         | `0.1.0`  |
| `murmur:process`         | `0.1.0`  |
| `murmur:task`            | `0.1.0`  |
| `murmur:task-io`         | `0.1.0`  |
| `murmur:conversation`    | `0.2.0`  |
| `murmur:stream`          | `0.1.0`  |
| `murmur:host`            | `0.1.0`  |
| `murmur:runtime`         | `0.4.0`  |
| `murmur:runtime-guest`   | `0.1.0`  |
| `murmur:driver`          | `0.6.0`  |

`SERVED_WIT_PACKAGES` in `capsule-runtime` (`src/wit_versions.rs`) must move with this table:
`cargo test -p capsule-runtime wit_versions` fails until it matches the `package` declarations under
`wit/`, and `mur doctor` compares installed artifacts against it.

## WIT levels

A WIT level is one number naming the exact set above. `levels.txt` in this directory lists every
level, one `<level> <package>@<version>` line per package, each level listing every package. The
newest level is the one this `mur` serves: `mur version` prints it, and load, install, search,
`mur index` and `mur doctor` decide fit against it.

The file is append-only. A committed level is never edited, renumbered or reused, so a level
number names the same package set on every `mur` and in every artifact's `murmur-build.json`.

A PR that moves any `murmur:*` package version must, in the same change:

1. Move the `package` declaration in every `.wit` file that declares it.
2. Move `SERVED_WIT_PACKAGES` in `src/wit_versions.rs` and the resolver constants it covers.
3. Append level `N+1` to `levels.txt`, listing every package at its new version.
4. Update the Current versions table above.

What enforces it:

| Check | Fails when |
|---|---|
| `cargo test -p capsule-runtime wit_levels` | `SERVED_WIT_PACKAGES` differs from the newest level, naming each differing package and the level to append |
| `murmur_artifact::parse_wit_levels`, through the same tests | A level is numbered out of order or with a gap, lists a package twice or out of order, or equals the level before it |
| `scripts/check-wit-versions.sh` (per PR) | The tree's declarations differ from the newest level |
| `scripts/wit-versions-changed-since.sh <rev>` (release gate) | A level committed at `<rev>` was edited, renumbered or removed |

The docs page `reference/wit-levels.md` generates its compatibility table from this file and the
`v*` release tags at docs build time.

`murmur:hook` started at `0.2.0` because its 9-function `lifecycle` interface
already reflected one prior additive evolution — the original 7-function baseline
plus `on-task-start`/`on-task-end`. Versioning retroactively named that step
rather than starting the package fresh at `0.1.0`.

`murmur:hook` then went to `0.3.0` when `compaction-event` gained
`model: option<string>` and `system-prompt: option<string>` — a field addition to
an existing record, which is always a major bump (see below). In the same step
`murmur:runtime` went to `0.2.0`: it gained a wholly new interface, `inference`,
whose single `run-inference` function the host provides as an *import* to any
hook that declares it (a hook that does not import it is unaffected), which is a
minor bump.

`murmur:hook` then went to `0.4.0` when `hook-output` gained a fifth case,
`reopen-task(string)` (the `on-task-end` control-return). `hook-output` is the
return type of every one of the nine `lifecycle` functions, so widening the
variant changes the wire shape of every export — a major bump. It was **not**
shippable as a purely additive no-version change: `TypedFunc::typed` is
structural and does not admit variant subtyping across the call boundary, so
lifting a pre-`reopen-task` four-case guest return against the new five-case host
type fails with "type mismatch with results". Hooks built against `@0.3.0` or
earlier stopped resolving at this bump and had to be rebuilt.

`murmur:hook` then went to `0.5.0` when `shell-event` gained `binary: string` —
the canonicalized path of the program a shell tool actually invoked. The record
previously carried only `command` (the argument list, never the binary name), so
no hook bound to `on-shell` could tell whether an event was `pytest`, `cargo` or
`ls`. Adding a field to an existing record is always a major bump, per the rule
below; the `0.3.0 → 0.4.0` entry above records what happens if you try to ship
one additively instead. Hooks built against `@0.4.0` or earlier stopped
resolving at this bump and had to be rebuilt.

`murmur:task-io` was created at `0.1.0` for the `read` interface — `input-len`
/ `read-input` / `output-len` / `read-output`, the host-provided window onto a
task's input and result text. The interface is a hook-side import, so the rule
below classifies it as a minor bump, which would have taken `murmur:runtime` to
`0.3.0`. That was not shippable: the package version is part of *every* instance
name in the package, so `murmur:runtime/inference@0.2.0` would have become
`@0.3.0`, and the host accepts exactly one version with no fallback — every
already-published hook importing `inference` would have stopped instantiating
until rebuilt. Leaving `murmur:runtime` at `0.2.0` while adding an interface to
it would have made this table untrue instead.

`murmur:hook` then went to `0.6.0` carrying three shape changes at once, so
the ecosystem pays for one rebuild window rather than three. `message` gained
`id: option<string>` and `source-id: option<string>`; `hook-output` gained a
sixth case, `seed-context(list<message>)`, **appended** so the existing
discriminants `0`–`4` keep their indices; and `task-start-event` gained
`budget-tokens`, `context-window` and `prior-tokens`, all `u64`. Each of the
three is independently a breaking change under the rule below, and each was
wanted by the same programme of work — bundling them means a hook author
rebuilds once and reads one changelog entry. That is the argument for the
bundle, and it is also the argument for refusing the next field: a bump whose
contents are open is a bump nobody can finish paying for. Hooks built against
`@0.5.0` or earlier stopped resolving at this bump and had to be rebuilt.

`murmur:runtime` went to `0.3.0` in the same step: it gained a wholly new
interface, `tokens`, whose single `count` function the host provides as an
ungated import to any hook that declares it. **This is the standing exception to
the new-package rule below.** That rule exists for one reason — bumping a package
renames every instance name in it, so taking `murmur:runtime` to `0.3.0` renames
`murmur:runtime/inference@0.2.0` to `@0.3.0` and forces a rebuild of every
published hook importing it. Here that rebuild is already forced, by the
`murmur:hook@0.6.0` bump above, and every hook binds the `hook` world as a whole.
The marginal cost of folding `tokens` into `murmur:runtime` is therefore exactly
zero, and the interface is `murmur:runtime/tokens.count` by the name the calling
programme asked for. That is the whole of the difference from the `murmur:task-io`
decision above, where no rebuild was already being paid for: when a new interface
arrives on its own, the rule below still applies and it goes in a new package at
`0.1.0`.

`murmur:conversation` was created at `0.1.0` for the `read` interface — the single
`read-messages` function serving the durable conversation record. It arrived on
its own, with no bump already being paid for elsewhere, so the rule below applies
and the `murmur:runtime@0.3.0` exception recorded above does not: folding it into
`murmur:runtime` would have renamed `murmur:runtime/inference@0.3.0` and
`murmur:runtime/tokens@0.3.0`, forcing a rebuild of every published hook to buy
nothing. `read` *uses* `murmur:hook/lifecycle`'s `message` record, which is
a reference and not a change: `murmur:hook` did not move for it, and the only edit
to it in the same step was the doc comment on `message.id`, a patch-tier change
that moves no version.

`murmur:hook` then went to `0.7.0` carrying the two shape changes a *decision-point*
dispatch of `on-tool-call` and `on-shell` needs, bundled so the ecosystem pays for one
rebuild window rather than two. `hook-output` gained a seventh case, `deny(string)`,
**appended** so the existing discriminants `0`–`5` keep their indices; and `shell-event`
and `tool-event` were each restructured around an `outcome: option<...>` field, whose
`none` is what tells a hook the call it is being shown has not run yet. The same step
moved every post-call field of those two records into the new `shell-outcome` and
`tool-outcome` records, and added `argv`/`script` to `shell-event` and `input` to
`tool-event` — the untruncated identity of what is about to run, which a policy hook must
decide on because `command` is a clipped display string. Each of these is independently a
breaking change under the rule below. Hooks built against `@0.6.0` or earlier stopped
resolving at this bump and had to be rebuilt.

`murmur:hook` then went to `0.8.0` when `shell-event` gained
`recipe: option<string>` — the body of the recipe a build-tool invocation names,
read out of the capsule's workdir by the runtime for `make <target>`,
`just <recipe>` and `npm run <script>`. Without it a policy gating `just build`
was deciding on a name, and a name can be redefined underneath the approval by
editing `justfile`. The field sits between `script` and `outcome`, which keeps
the identity fields together and keeps `outcome` last as the field that tells the
decision-point dispatch from the observation one. Adding a field to an existing
record is always a breaking change under the rule below. Hooks built against
`@0.7.0` or earlier stopped resolving at this bump and had to be rebuilt.

That field is the whole of this bump. Nothing else was bundled with it because
nothing else was pending: the `0.6.0` entry above records why a bundle is worth
paying for when several shape changes are already wanted, and equally why a bump
whose contents are open is a bump nobody can finish paying for. `murmur:runtime`
and `murmur:conversation` did not move. Both merely `use` `murmur:hook`'s
`message` record, which does not change shape here, so the `use` lines were
retargeted at `@0.8.0` and the packages themselves stayed where they were —
the same precedent the `murmur:conversation` entry above records.

`murmur:hook` then went to `0.9.0` when `message` gained
`inserted-by: option<context-insertion>`, with the new
`enum context-insertion { replace-context, seed-context }`. It names the hook
output a message entered the conversation through, which only the runtime knows
for certain, at the moment it commits a `replace-context` or a `seed-context`.
Without it a hook reading the conversation could not tell a compaction summary
from a person's turn: the shipped compaction hook returns its summary as
`role: "user"`, and `source-id` is hook-supplied and carries no provenance. The
field is appended last, after `source-id`. Adding a field to an existing record
is always a breaking change under the rule below.

The field is an enum rather than a boolean, and the boolean is declined. The
runtime commits two hook outputs that put messages into a conversation, and a
seeded message is no more a person's turn than a summary is. A single
`is-summary` flag would either leave seeds unmarked, so a seed still reads as a
person's turn, or mark them too, so a hook cannot tell a memory seed from a
summary. The enum carries both facts at the same cost. It names hook outputs,
not producers: a vocabulary of producers, such as the corpus records and
reloaded history that `source-id`'s doc groups with compaction summaries, is
declined as well. Reloaded history is not an insertion, since a reloaded line
keeps the mark it was written with. A corpus record reaches the conversation
only through a hook's `seed-context`, which is all the runtime can vouch for.
The two cases are closed: a third case is a breaking bump, like a new field.

That field and its enum are the whole of this bump. `canceled`, `truncated` and
`fence` stay marks on the conversation record's lines and are not on the WIT
record. `murmur:conversation` went to `0.2.0` and `murmur:runtime` went to
`0.4.0` in the same step, because the `message` record both `use` changed shape:
`read-messages` returns it and `inference-request` carries it. The `0.8.0` entry
above kept both packages still because `message` did not change there. Here it
does, and every hook rebuild is already forced by `murmur:hook@0.9.0`, so moving
the two siblings costs nothing extra: the same reasoning as the
`murmur:runtime@0.3.0` exception. Hooks built against `@0.8.0` or earlier stopped
resolving at this bump and had to be rebuilt.

The `deny` arm is the one `hook-output` case whose contract is *subtractive*: it can only
stop a call the manifest already permitted, and there is deliberately no arm that permits
one. Widening the variant with a permitting case would be a different kind of change from
every bump recorded above — it would let an artifact grant authority a manifest withheld —
and this entry records that no such case exists so a later bump does not add one by
analogy.

`murmur:driver` was created at `0.1.0` for the `process` interface — `describe`,
`launch`, `parse` and `classify-exit`, the contract a process driver exports so
the runtime can drive one harness CLI without knowing anything about it. Its
instance name is `murmur:driver/process@0.1.0`. It is a new package because
nothing existing moves: no published artifact exports or imports anything it
contains, so no instance name changes and nothing is rebuilt. Its world,
`process-driver`, imports nothing of Murmur's, because a process driver is
granted nothing; it lives in its own bindgen tree, `process-driver/`.

`murmur:driver` then went to `0.2.0` when the `process` interface learned to
carry what a harness spent. A new `usage` record — `input`, `output`,
`cache-read`, `cache-creation` and `thinking`, each `option<u64>` and each the
cumulative total for the harness run — arrived with a new `event` case,
`usage(usage)`, **appended** after `retry` so the existing discriminants `0`–`7`
keep their indices; and `description` gained `reports-usage: bool`, last, which
says whether the driver emits that case at all. Adding a case to a variant and a
field to a record are each independently a breaking change under the rule below.
Its instance name is now `murmur:driver/process@0.2.0`.

The bump was paid for by rebuilding: every driver built against `@0.1.0`,
including this repository's own `tests/fixtures/process-driver` components,
stopped loading and was rebuilt against `@0.2.0`. The host keeps no fallback, so
a `@0.1.0` driver is refused at staging with `E-WIT-LEVEL-001`: it does not fit
WIT level 1, it exports `murmur:driver/process@0.1.0`, and level 1 has
`murmur:driver@0.3.0`, with a hint to rebuild it against level 1's packages.
One `@0.1.0` component stays committed under
`crates/murmur-cli/tests/fixtures/process-driver-v1/`, together with a frozen
copy of the `@0.1.0` `.wit` it is built from, so that refusal stays testable
after this and every later bump. That copy lives in the fixture tree rather than
under `wit/`, because the one-version-per-package rule this file's checker
enforces is a rule about the tree the host is built from.

Nothing else was bundled with it, because nothing else was pending: the `0.6.0`
entry above records both why a bundle is worth paying for when several shape
changes are already wanted, and why a bump whose contents are open is a bump
nobody can finish paying for. `murmur:hook` did not move, and deliberately:
`inference-event` still carries `input-tokens: u64` / `output-tokens: u64` with
no optional form, so a `process` turn whose driver reported nothing is handed
`0`. Making those optional would break every published hook artifact to express
a distinction the trace already carries, which this bump declined to do.

`murmur:driver` then went to `0.3.0` when the `process` interface learned to
report a tool call while the model is still writing it. Two new records —
`tool-call-start` (`id`, `name`) and `tool-call-progress` (`id`,
`input-bytes: u64`, cumulative for the call) — arrived with two new `event`
cases, `tool-call-started(tool-call-start)` and
`tool-call-progress(tool-call-progress)`, **appended** after `note` so the
existing discriminants `0`–`11` keep their indices. Adding a case to a variant
is a breaking change under the rule below. Its instance name is now
`murmur:driver/process@0.3.0`.

The progress case carries a byte count, not a fragment of the input, on
purpose. The runtime turns both cases into stream frames, and
`murmur.WatchSession` may be held by a watch-only credential or be public.
Input streamed while the model writes it would reach that stream before a hook
or the call gate had seen the complete call and had the chance to deny it, and
would put on the ephemeral stream what the durable trace does not hold.
Carrying only a count across the interface means the runtime never holds a
call's input before the gate does.

The cost was paid knowingly. Every installed `@0.2.0` driver is refused at
launch with `E-WIT-LEVEL-001` the moment `mur` upgrades: it does not fit WIT
level 1, it exports `murmur:driver/process@0.2.0`, and level 1 has
`murmur:driver@0.3.0`, with a hint to pin a version that fits and run
`mur install`. That includes one pinned in a
`murmur.lock` that launched the day before, and every third-party process
driver. A pinned lock stops reproducing a launch it reproduced yesterday; that
was spent for a one-time correction while the ecosystem is small enough to
rebuild. Recovery is the refusal's hint: pin a release of the driver built
against `@0.3.0` and run `mur install`, or rebuild the driver. One `@0.2.0`
component stays committed under
`crates/murmur-cli/tests/fixtures/process-driver-v2/`, beside the `@0.1.0` one
and with its own frozen `.wit`, so the refusal of the version every published
driver was built against stays testable.

Nothing else was bundled with it. `description` did not gain a field declaring
that a driver sends the new cases: both are optional for a driver, so nothing
needs to declare them.

`murmur:stream` was created at `0.1.0` for the `events` interface, and
`murmur:text` was **retired** in the same step, with no fallback. `events` is
the one interface a tool or driver imports for everything it tells the host
while a turn is still in flight: `emit-chunk` and `emit-thinking-chunk`, carried
over unchanged from `murmur:text/chunks`, and two new functions,
`tool-call-started(id, name)` and `tool-call-input-bytes(id, bytes)`, through
which an http driver reports a tool call the model is writing. Its instance
name is `murmur:stream/events@0.1.0`. `stream` is a WIT keyword, so the source
spells the package `murmur:%stream`; the `%` is escaping and appears in no
instance name.

It is one package rather than a second one beside `murmur:text` because the
process transport already carries every mid-turn signal on one channel — the
single `variant event` of `murmur:driver/process` — and the http transport now
does the same. Tool-call signals do not belong on an interface named `chunks`
in a package named `text`, and leaving text where it was would have split one
channel across two packages.

It is a new package rather than a bump of `murmur:text` because the name
stopped being true: the interface no longer carries only text. A bump would
have cost the same rebuild — every driver importing `murmur:text/chunks@0.1.0`
stops linking either way — and kept a name that misdescribes what it holds.

The cost was paid knowingly. Every installed http driver importing any
`murmur:text` interface is refused at staging with `E-WIT-LEVEL-001`: it does
not fit WIT level 1, it imports `murmur:text/chunks@0.1.0`, and level 1 has no
version of `murmur:text`, with a hint to pin a version that fits and run
`mur install`. The check covers every
`runtime: driver` artifact a capsule declares, not only its inference driver,
because a `switch_driver` target is dispatched through the same linker. This
repository's own driver fixtures were rebuilt against `murmur:stream`. One
`murmur:text` component stays committed under
`crates/murmur-cli/tests/fixtures/streaming-driver-text/`, so the refusal stays
testable.

`murmur:driver` then went to `0.4.0`, WIT level 2, when the `process` interface
learned to carry what a harness reports about the person's subscription. A new
`quota-window` record — `name`, and `used` (a fraction from 0 to 1),
`resets-at` (unix milliseconds), `window-minutes` and `state`, each optional —
and a new `quota-state` enum (`ok`, `warning`, `exhausted`) arrived with two new
`event` cases, `quota(list<quota-window>)` and `suggested-prompt(string)`,
**appended** after `tool-call-progress` so the existing discriminants `0`–`13`
keep their indices. Adding a case to a variant is a breaking change under the
rule below: `TypedFunc::typed` does not admit variant subtyping, so a host
lifting a fourteen-case `event` list cannot call a driver built for sixteen, nor
the reverse. Its instance name is now `murmur:driver/process@0.4.0`.

The units are the interface's, not any harness's. One harness reports a window
as a 0–1 utilization with a reset in seconds, another as an integer percentage
on `primary`/`secondary` slots, and the runtime has no business knowing which:
the driver translates into fraction, milliseconds and a name it documents. A
report is a snapshot of the windows it names, and a later report replaces a
window of the same name whole, so a driver whose harness sends sparse updates
merges them into its own last report first. The runtime records and forwards
both events and acts on neither: no warning, stop, retry or prompt change
follows from a quota, however exhausted.

The same step made the lines a harness prints after its terminal event
readable. A harness emits its prompt suggestion after its result, so during the
existing exit grace the runtime keeps reading and parsing, taking only `quota`,
`suggested-prompt` and `note` from what it reads. That is a contract change
without a shape change, recorded on `parse` in the `.wit`.

The cost was paid knowingly. Every installed `@0.3.0` driver is refused at
launch with `E-WIT-LEVEL-001` the moment `mur` upgrades: it does not fit WIT
level 2, it exports `murmur:driver/process@0.3.0`, and level 2 has
`murmur:driver@0.4.0`, with a hint to pin a version that fits and run
`mur install`. Every process driver must be rebuilt. One `@0.3.0` component
stays committed under `crates/murmur-cli/tests/fixtures/process-driver-v3/`,
beside the `@0.1.0` and `@0.2.0` ones and with its own frozen `.wit`, so that
refusal stays testable.

Nothing else was bundled with it. `description` did not gain a field declaring
that a driver sends either case: both are optional for a driver, as
`tool-call-started` is, so nothing needs to declare them. Every other package
stays where level 1 had it, so an artifact whose interfaces are all outside
`murmur:driver` fits level 2 unchanged.

`murmur:tool` then went to `0.2.0`, WIT level 3, when `tool-result` gained
`attachments: list<attachment>`, with the new `record attachment { media-type:
string, path: string }`. A tool names in it the image files it wrote into the
work folder; the host checks each one's type and size, stores it beside the
conversation, and shows it to the model. Before this, a tool's only channels
back were text, so no model in a capsule could see a screenshot or a picture.
The field is appended last, after `metadata`. Adding a field to an existing
record is always a breaking change under the rule below: the canonical ABI lays
the record out by position, so a host lifting the seven-field record cannot
call a guest returning six, nor the reverse. Its instance name is now
`murmur:tool/run@0.2.0`.

`murmur:tool-registry` went to `0.2.0` in the same step, because its
`invoke` returns `result<tool-result, string>` and that record changed shape:
the same reasoning as the `murmur:conversation@0.2.0` and
`murmur:runtime@0.4.0` moves beside `murmur:hook@0.9.0`. Its instance name is
now `murmur:tool-registry/invoke@0.2.0`.

`murmur:runtime-guest` and `murmur:host` did not move. Each is a package of
worlds only: neither declares an interface, so its version is part of no
instance name any component imports or exports, and no host lookup reads it.
Their worlds were retargeted at `murmur:tool/run@0.2.0` and
`murmur:tool-registry/invoke@0.2.0`, which changes what a component built from
them carries, and that change is already recorded by the two bumps above. This
is the same decision the `0.8.0` entry above records for a package that only
`use`s another: the references move, the package does not.

The cost was paid knowingly. The `driver` world exports `murmur:tool/run`, so
every inference driver implements `tool-result` as well as every tool, and every
script capsule imports `murmur:tool-registry/invoke`. Every WASM tool, driver
and script capsule built for level 2 is refused at staging with
`E-WIT-LEVEL-001` the moment `mur` upgrades: it does not fit WIT level 3, it
exports `murmur:tool/run@0.1.0` or imports `murmur:tool-registry/invoke@0.1.0`,
and level 3 has `murmur:tool@0.2.0` and `murmur:tool-registry@0.2.0`. That is
every such artifact in the default-artifacts release current at the bump, until
it publishes level-3 builds; hooks and process drivers name neither package and
fit level 3 unchanged. One `@0.1.0` tool stays committed under
`crates/murmur-cli/tests/fixtures/tool-v1/`, so the refusal stays testable.

Nothing else was bundled with it. The image a tool returns is a file it names,
not bytes in the record: the record stays small whatever the image weighs, and
the host reads the file under the work folder's own rules. Media types beyond
the four the host checks, and a placement hint for the driver, were declined:
each would be a field nobody yet needs, and the next one is a breaking bump of
its own.

`murmur:process@0.1.0` was added on 2026-11-04, WIT level 4: the interface
`murmur:process/process@0.1.0`, through which a WASM tool runs a program or keeps
one running for the session under the rules a shell command obeys. It is a new
package under the "wholly new interface" rule below. The `tool` world in
`murmur:runtime-guest` imports it, and the host binds it through a new
`tool-host` world in `murmur:host`; both are world-only packages whose version is
part of no instance name, so both stay `0.1.0`. No instance name changed and no
committed component was rebuilt: a component imports only the interfaces its
source calls, so a tool built against the extended `tool` world that does not
call `murmur:process` names exactly what it named before. An artifact therefore
fits level 4 exactly when it fits level 3, unless it imports the new package,
which fits level 4 only.

`murmur:driver` then went to `0.5.0`, WIT level 5, so that `mur doctor` can ask
whether a process capsule's harness is logged in for this user without spending
a turn. Only the driver knows how its harness reports a login, so the question
goes through the driver: `description` gained a last field, `login-check`, a new
variant that is either `command(list<string>)`, the arguments after the harness
binary that make it report its login, or `unavailable(string)`, the reason it
cannot; and the interface gained a function, `read-login`, which turns a new
`login-output` record (`exit-code`, `stdout`, `stderr`) into a new `login-state`
variant (`logged-in`, `logged-out(string)`, `unknown(string)`). The runtime runs
the command exactly as it runs the version check, hands what it printed to the
driver, and prints only the driver's reason. A field appended to a record and a
function added to an interface are both breaking under the rule below, so the
minor moved. Its instance name is now `murmur:driver/process@0.5.0`.

An optional extra export, which a driver could leave out, was declined: the
host would have to ask whether a driver exports it before calling it, which is
the feature detection the next section rules out. A `command` naming no
arguments is refused when the driver loads, since a bare harness binary may
start a session. `mur run` asks nothing new at launch: a logged-out harness
still fails its first turn.

The cost was paid knowingly. Every installed `@0.4.0` driver is refused at
launch with `E-WIT-LEVEL-001` the moment `mur` upgrades: it does not fit WIT
level 5, it exports `murmur:driver/process@0.4.0`, and level 5 has
`murmur:driver@0.5.0`. Every published process driver, `driver-claude-code`
among them, must be rebuilt against `0.5.0`. One `@0.4.0` component stays
committed under `crates/murmur-cli/tests/fixtures/process-driver-v4/`, beside
the older ones and with its own frozen `.wit`, so that refusal stays testable.
No other package moved, so an artifact whose interfaces are all outside
`murmur:driver` fits level 5 exactly when it fits level 4.

`murmur:driver` then went to `0.6.0`, WIT level 6, so a process driver can
declare what its harness needs beyond what the capsule's class gives every
command — the directories it reads and writes, the hosts it connects to — which
the runtime grants only where the capsule manifest also allows it. `description`
gained a last field, `harness-needs`, a new `harness-needs` record carrying
`paths: list<path-need>` (each a new `path-need` record of a `path` and a new
`path-access` enum, `read` or `read-write`) and `hosts: list<string>` (each
spelled as a `capabilities.network.allow` entry). The environment half stays
`required-env`. A field appended to a record and new records and an enum are
breaking under the rule below, so the minor moved. Its instance name is now
`murmur:driver/process@0.6.0`. A malformed need is refused when the driver loads
(`E-RUN-032`); a need the manifest does not allow refuses the launch
(`E-CAP-021`). An empty `harness-needs` is valid and declares nothing.

The cost was paid knowingly. Every installed `@0.5.0` driver is refused at
launch with `E-WIT-LEVEL-001` the moment `mur` upgrades: it does not fit WIT
level 6, it exports `murmur:driver/process@0.5.0`, and level 6 has
`murmur:driver@0.6.0`. Every published process driver, `driver-claude-code`
among them, must be rebuilt against `0.6.0` — the rebuild that declares Claude
Code's needs. One `@0.5.0` component stays committed under
`crates/murmur-cli/tests/fixtures/process-driver-v5/`, beside the older ones and
with its own frozen `.wit`, so that refusal stays testable. No other package
moved, so an artifact whose interfaces are all outside `murmur:driver` fits
level 6 exactly when it fits level 5.

**A wholly new interface added to a package that already has published
consumers goes in a new package at `0.1.0`.** No existing instance name changes,
no artifact is rebuilt, and this table simply gains a row. Bump an existing
package only when the change touches something that package already ships —
*unless* the same change set already forces a rebuild of every consumer of that
package, in which case folding the interface in costs nothing extra and the
package is bumped instead. `murmur:runtime@0.3.0` is that case, recorded above.

## When to bump

### Patch (`x.y.Z`)

Documentation or comment-only edits with **no** shape or dispatch change: a
reworded doc comment, a clarified reserved-key note, whitespace. Nothing about
the binary ABI or the set/signature of exported functions changes.

### Minor (`x.Y.0`)

Adding a **wholly new** function, or a wholly new interface, that the host treats
as *optional to export* — generalizing the `OPTIONAL_HOOK_FNS` pattern in
`src/hooks.rs`, where a component compiled before the function existed simply
omits it and is never dispatched for it. The `on-task-start`/`on-task-end`
additions that took `murmur:hook` from its 7-function baseline to `0.2.0` are the
canonical example.

A minor bump is **never** the right level for:

- adding a field to an existing `record`,
- inserting a case into an existing `variant`,
- widening or narrowing an existing type.

The Component Model's binary ABI has no structural tolerance for compound-type
shape changes: every field/case is positional in the canonical ABI, so any such
edit is a breaking change even though it "looks additive" in the source. These
are always **major** bumps.

### Major (the breaking tier)

Every `murmur:*` package is pre-1.0, where semver makes the **minor** field the
breaking axis: a breaking change to `0.4.0` ships as `0.5.0`, not `1.0.0`. This
tier is called *major* throughout this document in the sense of "breaking", and
that is what every bump recorded above has done.

Any of:

- a signature change to an existing function (parameters or return type),
- any shape change to an existing `record` or `variant` (add/remove/reorder a
  field or case, change a member type),
- removing or renaming an existing function or interface.

A host built for the old major version cannot safely call a component built for
the new one, and vice versa.

## No compatibility fallbacks

The host accepts **exactly one version of each interface** — the version
declared in this tree — and keeps no fallback for any earlier one. There is no
compat-shim layer, no version-keyed dispatch, and no transition window.

A bump therefore requires every affected artifact to be rebuilt and
republished. An artifact built against a retired version does not silently
degrade: it fails at instantiation with an error naming the interface the host
expects and pointing the author at a rebuild. That is deliberate — a loud
failure with a known fix is preferable to a compatibility layer that accretes
one shim per bump and is never removed.

Practically, this means the cost of a bump is paid at bump time, by whoever
makes it:

1. Bump the version here.
2. Rebuild this repository's own committed components with
   `scripts/rebuild-components.sh` from the repository root, and commit them.
   `scripts/rebuild-components.sh --check` names every one the bump left
   stale, with the command that rebuilds it; the components and the WIT
   subtree each builds against are listed in
   [components.list]components.list.
3. Rebuild the artifacts in `default-artifacts` (see the sync note in
   [README.md]README.md), and republish.

Do not reach for a fallback to defer that work.

If you are reviewing a change that adds a version fallback of any kind — a
compat struct mirroring an old shape, a `match` on a legacy version, a
try-new-then-old resolution chain — that is the thing this policy exists to
reject.

## Unversioned artifacts: fallback removed

Artifacts published before WIT package versioning exported the **unversioned**
instance names (`murmur:capsule/run`, `murmur:tool/run`,
`murmur:hook/lifecycle`). A transitional dual-accept runtime
resolved those interfaces by trying the versioned instance name first and
falling back to the bare unversioned name, so already-published artifacts kept
running unmodified during the transition window.

That fallback has been **removed**. The host now resolves the three
dynamically-instantiated guest interfaces by the **versioned** instance name
only:

- `src/hooks.rs` — `resolve_lifecycle_iface` resolves the single
  `LIFECYCLE_IFACE` name and nothing else: no earlier version, and not the bare
  unversioned name.
- `src/runtime.rs` — `resolve_versioned_iface` resolves
  `murmur:capsule/run@0.1.0` and `murmur:tool/run@0.1.0`.

The host-provided *import* interfaces (`murmur:tool-registry/invoke@0.1.0`,
`murmur:stream/events@0.1.0`, `murmur:task/task@0.1.0`,
`murmur:runtime/inference@0.4.0`, `murmur:runtime/tokens@0.4.0`,
`murmur:task-io/read@0.1.0`, `murmur:conversation/read@0.2.0`) are likewise
registered under the versioned name only.

An artifact that still exports (or imports) only the unversioned name now
**fails to instantiate** with a hard error naming the versioned interface the
host expected and pointing at rebuilding — `mur install` for a default artifact,
or a source rebuild otherwise. Rebuild and republish any such artifact.