@plurnk/plurnk-service 0.51.0 → 0.53.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 (60) hide show
  1. package/SPEC.md +17 -11
  2. package/dist/core/Engine.d.ts.map +1 -1
  3. package/dist/core/Engine.js +124 -67
  4. package/dist/core/Engine.js.map +1 -1
  5. package/dist/core/Engine.sql +41 -11
  6. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  7. package/dist/core/SchemeRegistry.js +1 -2
  8. package/dist/core/SchemeRegistry.js.map +1 -1
  9. package/dist/core/git-membership.js +1 -1
  10. package/dist/core/git-membership.js.map +1 -1
  11. package/dist/core/packet-wire.d.ts +1 -2
  12. package/dist/core/packet-wire.d.ts.map +1 -1
  13. package/dist/core/packet-wire.js +111 -138
  14. package/dist/core/packet-wire.js.map +1 -1
  15. package/dist/core/run-ops.sql +5 -0
  16. package/dist/core/teaching.d.ts +1 -1
  17. package/dist/core/teaching.d.ts.map +1 -1
  18. package/dist/core/teaching.js +6 -4
  19. package/dist/core/teaching.js.map +1 -1
  20. package/dist/schemes/Exec.d.ts.map +1 -1
  21. package/dist/schemes/Exec.js +11 -24
  22. package/dist/schemes/Exec.js.map +1 -1
  23. package/dist/schemes/Log.d.ts.map +1 -1
  24. package/dist/schemes/Log.js +3 -2
  25. package/dist/schemes/Log.js.map +1 -1
  26. package/dist/schemes/Plurnk.js +1 -1
  27. package/dist/schemes/Plurnk.js.map +1 -1
  28. package/dist/schemes/Run.d.ts +4 -2
  29. package/dist/schemes/Run.d.ts.map +1 -1
  30. package/dist/schemes/Run.js +86 -51
  31. package/dist/schemes/Run.js.map +1 -1
  32. package/dist/schemes/_entry-crud.sql +12 -0
  33. package/dist/schemes/_entry-find.d.ts +4 -17
  34. package/dist/schemes/_entry-find.d.ts.map +1 -1
  35. package/dist/schemes/_entry-find.js +74 -66
  36. package/dist/schemes/_entry-find.js.map +1 -1
  37. package/dist/schemes/_entry-graph.d.ts +0 -6
  38. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  39. package/dist/schemes/_entry-graph.js +0 -8
  40. package/dist/schemes/_entry-graph.js.map +1 -1
  41. package/dist/schemes/_entry-graph.sql +0 -10
  42. package/dist/schemes/_entry-manifest.d.ts +13 -2
  43. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  44. package/dist/schemes/_entry-manifest.js +84 -89
  45. package/dist/schemes/_entry-manifest.js.map +1 -1
  46. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  47. package/dist/schemes/_entry-ops.js +14 -6
  48. package/dist/schemes/_entry-ops.js.map +1 -1
  49. package/dist/schemes/_entry-ops.sql +11 -0
  50. package/dist/server/Daemon.d.ts.map +1 -1
  51. package/dist/server/Daemon.js +5 -8
  52. package/dist/server/Daemon.js.map +1 -1
  53. package/docs/log.md +1 -1
  54. package/migrations/0000-00-00.01_schema.sql +3 -1
  55. package/package.json +16 -15
  56. package/requirements.md +5 -3
  57. package/dist/schemes/exec-receipt.d.ts +0 -4
  58. package/dist/schemes/exec-receipt.d.ts.map +0 -1
  59. package/dist/schemes/exec-receipt.js +0 -25
  60. package/dist/schemes/exec-receipt.js.map +0 -1
package/SPEC.md CHANGED
@@ -146,7 +146,7 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
146
146
 
147
147
  **The keystone's first use: operator reference docs.** `PLURNK_MD_<ALIAS>=<path>` (§operator-config) materializes `<path>` as a `plurnk:///<ALIAS>.md` entry — a `dispatchAsPlurnk` EDIT in the plurnk run, **not** the model's — and the model's turn-0 foists a READ of it. The model reads the doc inline while the materializing EDIT stays out of its log: idiomatic context injection, an ordinary entry + READ rather than a bespoke packet section. The same `PLURNK_MD_*` convention cascades to clients. {§actor-boundary-doc-injection}
148
148
 
149
- **Manifest preview.** `PLURNK_MANIFEST_ITEMS` foists a turn-0 READ of `plurnk:///manifest.json` into the model's first turn the same plurnk-origin foist as the docs so a run opens with the session catalog instead of blank. `-1` reads the full manifest; a positive `N` slices to the first N items (jsonpath `$[0:N]` the catalog is JSON); unset / `0` foists nothing. The READ is sequenced *after* the per-turn manifest write, so it hits the catalog rather than 404ing. {§actor-boundary-manifest-preview}
149
+ **Catalog preview.** `PLURNK_MANIFEST_ITEMS` foists a turn-0 `FIND(scheme:///**)` — one per scheme that holds entries — into the run's first turn, the same plurnk-origin foist as the docs, so a run opens with its catalog instead of blank. `-1` foists each scheme's whole catalog; a positive `N` caps each to its first `N` rows (FIND's `<L>`, clamped to the scheme's count so the strict marker never 416s); unset / `0` foists nothing. `log://` is absent it is present-mode (the `# Log` section), not a catalog scheme. {§actor-boundary-manifest-preview}
150
150
 
151
151
  ### §machine-processes The machine and its processes: session, run, fork
152
152
 
@@ -556,7 +556,7 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
556
556
  - `body` matcher operates on entry content (glob/regex/jsonpath/xpath), per grammar plurnk.md §"Body matcher dispatch"; the path-glob lives in the (target), not the body. {§find-glob-filter-on-content}
557
557
  - `signal` is a tag filter; entries match if they have ALL listed tags. {§find-tag-filter-and-semantics}
558
558
  - Session + scheme scoped — no cross-session/cross-scheme leakage. {§find-scoped-isolation}
559
- - Returns `FindResult { status, content, mimetype, results: Finding[] }`. A **finding** is its enclosing structural unit, not a bare path: `Finding { path, extent: {first,last}|null, symbol?, content? }` `extent` is the `<L>` line span (a whole-entry finding is `null`), `symbol` names the unit when known. `content` renders the findings as usable addresses, one per line `path<extent> (symbol)`, e.g. `known:///auth.ts<10,25> (login)`, or the bare path when `extent` is null (`text/plain`); the model READs an address to pull that region into its log. Extent resolution is per dialect: a content match (glob/regex/jsonpath/xpath) resolves each hit line to its smallest enclosing `symbol_defs` row (the bare line when none covers it); `~`semantic carries the best-matching chunk's span; `@`graph and a body-less FIND yield whole-entry findings. {§find-result-findings}
559
+ - Returns `FindResult { status, content, mimetype, results: CatalogEntry[] }`. FIND resolves to the scheme's **catalog rows** the very rows the manifest catalogs filtered to the statement's matches and kept in match order. A **catalog row** is `{ path, seconds?, tags?, channels: { <uri>: { mimetype, tokens, lines } } }`: the addressable entry path, its per-channel `{mimetype, tokens, lines}` keyed by addressable URI (default channel the bare path, non-default → `path#channel`), plus the entry's `tags` and a live `seconds` stream age. The matcher (glob/regex/jsonpath/xpath, `~`semantic, `@`graph) decides WHICH entries appear and in what order a content hit **includes** the entry, a miss **excludes** it; it never reshapes the row. There is no per-match extent: the match LOCATION (which line or symbol) is a `READ` concern, not a FIND field. `content` is the rows as a JSON array (`application/json`); a body-less `FIND(scheme:///**)` yields the scheme's whole catalog the manifest's per-scheme slice. {§find-result-catalog-rows}
560
560
 
561
561
  ### §send SEND
562
562
 
@@ -573,7 +573,9 @@ Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI).
573
573
 
574
574
  **Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose** (lifecycle §proposal): the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec:///<runtime>/<loop>/<turn>/<seq>` entry (the executor is the URI authority; the coordinate that follows matches the op's log-row coordinate, e.g. `exec:///sh/1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive what the model sees at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
575
575
 
576
- A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run** in-process: no proposal, no human gate, no notification. The run is awaited synchronously and its output's RECEIPT the `<tag>:///<coord>` address + a structural `OrientIndex` (counts/shape/keys/headings, never the content) rides back as the EXEC result body the same turn; the model READs the address to pull content. Receipt-only (read/pure included) is the containment that keeps tool output off the packet #240. {§exec-readpure-inline}
576
+ A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run**: no proposal, no human gate, no notification. It skips the gate a host command faces, but it does NOT resolve in-bandlike every exec it backgrounds and streams, its output reaching the model through the environment-observation injector (a foisted READ of the stream's new bytes each turn, §exec-stream), never a same-turn receipt. {§exec-readpure-ungated}
577
+
578
+ **Stream surfacing.** An exec's output is *observed, not fetched*. Each turn the environment-observation injector — the same machine §env-delta rides — reads each of the run's open channels from a per-channel byte cursor and foists the new bytes as an `origin=plurnk` READ at `<runtime>:///<coord>#<channel>`, then advances the cursor — each delta carries the `startLine` that cursor implies, so a stream spanning turns numbers into one continuous sequence (lines 1–k, then k+1–m), not a fresh `1:` each turn. The delta is **folded** while the channel streams and auto-**opened** on the terminal one (the channel closed): a model ignores a chatty background run but always SEES a finished one. It never types these READs — it consumes them. The EXEC row itself renders the *command* it ran, `:::`-fenced and line-numbered per §render-rule so the model can line-reference its own code — the input, distinct from the stream above (the output). This is exec as an instance of one ambient machine, env-delta as another (sibling edits, timestamp cursor, always folded). {§exec-stream}
577
579
 
578
580
  `SEND[499](exec:///<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§stream-control).
579
581
 
@@ -1185,7 +1187,7 @@ The CAS is the **hard backstop**, at the moment of writing, on every accept path
1185
1187
  type PacketSection = {
1186
1188
  name: string; // stable id: definition, tools, schemes, log, prompt, budget, errors, git, requirements — or a plugin's own
1187
1189
  slot: "system" | "user"; // the prompt-cache boundary; system-slot sections build the cache-stable system message
1188
- header: string | null; // "# Plurnk System X", or null (definition renders verbatim)
1190
+ header: string | null; // "## Plurnk System X", or null (definition renders verbatim)
1189
1191
  content: string; // rendered markdown — what the model saw
1190
1192
  tokens: number; // measured render-weight
1191
1193
  };
@@ -1202,7 +1204,7 @@ The wire projection (`PacketWire.renderSlot`) groups sections by slot into the s
1202
1204
 
1203
1205
  **Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `plurnk:///prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's prompt body materializes into the `prompt` section; the entry itself stays READ/FOLD-able like any other. The foisted `EDIT`'s **log row is folded by default** (`expanded=0`): the prompt body already lives in the `prompt` section, so the log keeps the action for forensics while collapsing the duplicate body, re-OPENable like any fold (§open-fold). {§prompt-fold}
1204
1206
 
1205
- **The entry catalog.** `plurnk:///manifest.json` is a real session entry the model READs to discover what's availablerewritten every turn as a live view of the full entry set. Built in the schemes layer (`_entry-manifest`) and materialized like any entry (the engine only orchestrates the per-turn write the same pattern as git membership), so it's READable and queryable. Body is `application/json`: a flat, **complete, unranked** array — one item per entry across all schemes, every entry listed in no relevance order, each `{ path, tags?, channels: { <uri>: { mimetype, tokens, lines } } }` — every channel keyed by the URI the model READs (the default channel by the bare path, a non-default by `path#channel`), so it reaches a channel without guessing. `tags` is present only when the entry carries `entry_tags` — its own categorization, surfaced so the model sees it in the directory and can `FIND` by tag without a separate read. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's write-time count (budget depth), `lines` the content extent from `Mimetypes.process().totalLines`. The engine counts neither. It does not list itself. {§packet-manifest-catalog}
1207
+ **The entry catalog.** The catalog is the **complete, unranked directory** of what a session holds, served by `FIND(scheme:///**)` one per-scheme array, queried on demand, not a single materialized entry (there is no `plurnk:///manifest.json`; the per-scheme arrays replaced it). Built in the schemes layer (`_entry-manifest.catalogRowsFor`); a per-turn derivation pump (`maintainDerivations`) refreshes the deep channels the rows report. A scheme's array is **every entry it holds, in no relevance order**, each `{ path, seconds?, tags?, channels: { <uri>: { mimetype, tokens, lines } } }` — every channel keyed by the URI the model READs (the default channel by the bare path, a non-default by `path#channel`), so it reaches a channel without guessing. `tags` is present only when the entry carries `entry_tags` — its own categorization, surfaced so the model can `FIND` by tag. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's live count recounted at render, `lines` the content extent from `Mimetypes.process().totalLines`. The catalog never lists itself. {§packet-manifest-catalog}
1206
1208
 
1207
1209
  ### §telemetry user.telemetry — model-facing runtime telemetry
1208
1210
 
@@ -1217,7 +1219,7 @@ Telemetry the model MUST react to immediately. Errors render in their own `error
1217
1219
 
1218
1220
  - `budget` per §tokenomics: turn-weight and heaviest-entries tables with `tokenCeiling`/`tokenUsage`/`tokensFree`.
1219
1221
  - `errors[]` from previous turn's dispatch. Required: `kind` discriminator. Additional kind-specific fields are flat on the element — NO nested `detail`. Canonical-JSON serialization sorts keys for prefix-cache friendliness.
1220
- - Wire: one `* {canonical-JSON}` line per error under `# Plurnk System Errors`, push order. Buffer drains on read. {§telemetry-drain-on-read}
1222
+ - Wire: one `* {canonical-JSON}` line per error under `## Plurnk System Errors`, push order. Buffer drains on read. {§telemetry-drain-on-read}
1221
1223
  - **No prose `message` field.** Errors carry structured facts. The `kind` is the alert; the named fields are the data. Guidance, advice, hints, and exhortation MUST NOT appear in telemetry. Letting the model infer what to do from facts (and the log) beats handing it instructions it will second-guess.
1222
1224
  - **Gamification policy (rummy precedent, plugins/error/error.js).** The model sees errors that **happened** — its actions failed, its emission didn't parse, its ops were truncated. The model does NOT see the engine's accounting *about* errors: strike streaks, cycle detection, sudden-death thresholds, no-ops bookkeeping. Surfacing internal state creates a gamification surface where the model optimizes for engine metrics (manufacturing a clean turn to reset the strike counter, e.g.) instead of the task. Engine bookkeeping drives abandonment silently; the model just sees its actual failures.
1223
1225
 
@@ -1240,23 +1242,23 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
1240
1242
 
1241
1243
  ### §tools user.tools — the capability sheet
1242
1244
 
1243
- The tools capability lines render **titleless**, directly under the `definition` (plurnk.md) section — the examples flow on from plurnk.md with no separate header — and **above** `# Plurnk System Requirements`, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the section is omitted when nothing is enabled. {§tools-capability-sheet}
1245
+ The tools capability lines render **titleless**, directly under the `definition` (plurnk.md) section — the examples flow on from plurnk.md with no separate header — and **above** `## Plurnk System Requirements`, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the section is omitted when nothing is enabled. {§tools-capability-sheet}
1244
1246
 
1245
- **Contributors: the wired executor tags.** Each available executor tag *with an example* contributes ONE line — its canonical usage — plus a `(docs: plurnk://docs/<tag>.md)` pointer when its package ships documentation, via the shared `teachingLine` (identical shape to the scheme directory, §schemes). A tag with no example contributes nothing; `PLURNK_DOCS_EXCLUDE` drops a named tag's line + doc. The boot `ExecutorRegistry` probes availability per tag, retiring the model's blind `<<EXEC[sh]…`.
1247
+ **Contributors: the wired executor tags.** Each available executor tag *with an example* contributes ONE line — its canonical usage — via the shared `teachingLine` (identical shape to the scheme directory, §schemes); its doc is materialized at `plurnk://docs/<tag>.md` and discovered via the turn-1 `FIND(plurnk://docs/**)` foist, not linked inline (#270). A tag with no example contributes nothing; `PLURNK_DOCS_EXCLUDE` drops a named tag's line + doc. The boot `ExecutorRegistry` probes availability per tag, retiring the model's blind `<<EXEC[sh]…`.
1246
1248
 
1247
1249
  ### §schemes user.schemes — the scheme directory
1248
1250
 
1249
- A `# Plurnk System Schemes` section renders in the system slot **after the definition (plurnk.md — grammar + imperatives) and the tools sheet** — a terse directory of the scheme families available this session, so the model knows what URI schemes exist before it acts. Each scheme that ships a `manifest.example` contributes ONE line — its canonical usage (no scheme prefix; the example self-documents) — plus a `(docs: plurnk://docs/<scheme>.md)` pointer when a doc exists. The in-tree core schemes author their depth in `docs/<name>.md` (loaded at boot, shipped with the package); daughter schemes ship `manifest.documentation`. The verbose semantics live in that pull doc (materialized like any entry, READ on demand), not the hot path — terse pushes, depth pulls, via the same `teachingLine` as the tools sheet (§tools). A scheme with no example (provisional) is omitted; `PLURNK_DOCS_EXCLUDE` drops a named scheme's line + doc. {§schemes-directory}
1251
+ A `## Plurnk System Schemes` section renders in the system slot **after the definition (plurnk.md — grammar + imperatives) and the tools sheet** — a terse directory of the scheme families available this session, so the model knows what URI schemes exist before it acts. Each scheme that ships a `manifest.example` contributes ONE line — its canonical usage (no scheme prefix; the example self-documents). The doc is NOT linked inline (#270) it is materialized at `plurnk://docs/<scheme>.md` and discovered via the turn-1 `FIND(plurnk://docs/**)` foist, keeping the raw packet free of doc links. The in-tree core schemes author their depth in `docs/<name>.md` (loaded at boot, shipped with the package); daughter schemes ship `manifest.documentation`. The verbose semantics live in that pull doc (materialized like any entry, READ on demand), not the hot path — terse pushes, depth pulls, via the same `teachingLine` as the tools sheet (§tools). A scheme with no example (provisional) is omitted; `PLURNK_DOCS_EXCLUDE` drops a named scheme's line + doc. {§schemes-directory}
1250
1252
 
1251
1253
  ### §inject system.inject — the operator injection
1252
1254
 
1253
- When `PLURNK_PACKET_INJECT` names a readable markdown file, its content renders as a `# Plurnk Operator Notes` section in the system slot **right after the teaching** (definition → tools → schemes → inject), before the log — part of the cached prefix. Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard (a deliberate setting with a broken path is a misconfig, surfaced not hidden). `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section. {§packet-inject}
1255
+ When `PLURNK_PACKET_INJECT` names a readable markdown file, its content renders as a `## Plurnk Operator Notes` section in the system slot **right after the teaching** (definition → tools → schemes → inject), before the log — part of the cached prefix. Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard (a deliberate setting with a broken path is a misconfig, surfaced not hidden). `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section. {§packet-inject}
1254
1256
 
1255
1257
  **The scheme self-doc contract.** `example` is the hot-path one-liner; `documentation` is the deep doc — the exact shape execs already use (`example` + `documentation`). `SchemeRegistry.teach()` renders the directory; `docEntries()` materializes the docs (per loop.run, alongside the operator docs). `documentation` rides a service-side `SchemeManifest` extension until plurnk-schemes#25 lands it in the contract.
1256
1258
 
1257
1259
  ### §requirements The requirements section — static per-turn rules
1258
1260
 
1259
- Rendered at the END of the user packet under `# Plurnk System Requirements` {§requirements-requirements-render-last} — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. The header is omitted entirely when the requirements string is empty. {§requirements-requirements-omitted-when-empty} Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`"). The op syntax leads the section. PLAN is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to lead with `<<PLAN`), so the service injects no separate plan directive here.
1261
+ Rendered at the END of the user packet under `## Plurnk System Requirements` {§requirements-requirements-render-last} — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. The header is omitted entirely when the requirements string is empty. {§requirements-requirements-omitted-when-empty} Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`"). The op syntax leads the section. PLAN is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to lead with `<<PLAN`), so the service injects no separate plan directive here.
1260
1262
 
1261
1263
  **Sourcing:** caller supplies the string via `runLoop({ requirements })` / `runTurn({ requirements })`. Plurnk-service exposes `PATHS.defaultRequirements` (resolves `PLURNK_REQUIREMENTS` env → in-package `requirements.md`). No DB cascade — same string every turn.
1262
1264
 
@@ -1376,6 +1378,10 @@ Same rule applies across Known, Unknown, Skill, Plurnk, File. Effective mimetype
1376
1378
  - **Line-navigable** (text/markdown, text/plain, csv, source code, yaml, toml) → `N:\t` line-number prefix per line {§render-rule-line-navigable-prefix}
1377
1379
  - **Tree-navigable** (application/json, application/xml, text/html, +json/+xml suffixes) → verbatim body (no `N:\t` — outer line numbers would collide with structural navigation like jsonpath/xpath) {§render-rule-tree-navigable-verbatim}
1378
1380
 
1381
+ A log row renders its **result body** for the content-returning ops — `READ@200` (the content it pulled) and `FIND@200` (the catalog rows / matched entries it returned) — under the query's fence, mimetype-driven per the rules above; every other op re-emits its statement. FIND included: the model must see what a find *returned*, not just its echoed query, and the turn-0 foisted `FIND(scheme:///**)` reaches the packet through this branch — without it the catalog preview is invisible. {§render-rule-find-renders-result}
1382
+
1383
+ An `EDIT` log row renders its **resulting span** — the edited area as it looks now (`rx.span`), under the target's fence — not the input statement: the log reads "and here's X now," so the model sees its edit's effect. The meta line still carries op + target; the model's own EDITs and the system delta-EDITs (§env-delta) render identically; an emptied span → meta line only. With no span stored, the row falls back to re-emitting the statement (the heredoc the model wrote). {§edit-result-render}
1384
+
1379
1385
  The `N:\t` prefix is presentation/reference per plurnk.md ("not part of the source"); stripped before any matcher operation on the log entry.
1380
1386
 
1381
1387
  ### §markdown-primitive Mimetype primitive: text/markdown
@@ -1 +1 @@
1
- {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
@@ -576,12 +576,11 @@ class Engine {
576
576
  nextActionIndex++;
577
577
  }
578
578
  }
579
- // plurnk:///manifest.json rewritten EVERY turn (a live view of the
580
- // entry set, which changes each turn). A derived view (computed each
581
- // turn), NOT an action written directly (Engine.inject's path): no log entry,
582
- // no sequence slot, not dispatched. The catalog body is built in the
583
- // schemes layer (_entry-manifest); the engine only orchestrates the
584
- // per-turn write. Does not list itself.
579
+ // The per-turn derivation pump (_entry-manifest.maintainDerivations) refreshes
580
+ // every entry's deep channels (symbols/refs/embeddings/FTS, deep_hash-gated) so the
581
+ // catalog and FIND read current data. NOT an action: no log entry, no sequence slot,
582
+ // not dispatched. There is no plurnk:///manifest.json entry the catalog is served
583
+ // on demand by FIND(scheme:///**), foisted into the run's first turn below.
585
584
  const systemCtx = {
586
585
  db: this.#db, sessionId, runId, loopId, turnId,
587
586
  writer: "plurnk",
@@ -597,40 +596,64 @@ class Engine {
597
596
  // prompt-composition (EMI is eager + exhaustive — git is the only bound). When the
598
597
  // session's project_root is a git working tree, tracked files are
599
598
  // members without a client `add`; active members are materialized
600
- // (disk → body channel) so they appear in the manifest below. No-ops
601
- // on headless / non-git sessions. Runs BEFORE the manifest write so
599
+ // (disk → body channel) so they appear in the catalog. No-ops
600
+ // on headless / non-git sessions. Runs BEFORE the derivation pump so
602
601
  // this turn's packet reflects them.
603
602
  const fsDivergences = await GitMembership.indexGitMembership(systemCtx);
604
603
  await this.#logFsFictions(sessionId, fsDivergences);
605
- await EntryCrud.writeEntry("/manifest.json", {
606
- channels: { body: { content: await EntryManifest.buildManifestBody(systemCtx), mimetype: "application/json" } },
607
- tags: [],
608
- }, systemCtx, "plurnk");
609
- // Manifest preview (PLURNK_MANIFEST_ITEMS, §actor-boundary-manifest-preview):
610
- // a turn-0 foisted READ of the just-built catalog so a run opens with what's
611
- // available, not blank. -1 → full; N → the first N items (jsonpath slice — the
612
- // manifest is JSON); off by default. AFTER the manifest write so the READ hits
613
- // it, not a 404; same plurnk-origin foist as the operator docs.
604
+ await EntryManifest.maintainDerivations(systemCtx);
605
+ // Turn-0 catalog preview (PLURNK_MANIFEST_ITEMS, §actor-boundary-manifest-preview):
606
+ // one FIND(scheme:///**) per scheme that holds entries, foisted into the run's first
607
+ // model turn so it opens with its catalog (the per-scheme arrays that replaced the
608
+ // single manifest.json). -1 → each scheme's whole catalog; N → its first N rows
609
+ // (clamped to the scheme's count so FIND's strict <L> never 416s); off by default.
614
610
  if (seq === 1) {
615
611
  // #231 — a session's client-chosen manifestItems REPLACES the env default outright.
616
612
  const { manifestItems: sessionMI, autoReadAgents } = await SessionSettings.read(this.#db, sessionId);
617
613
  const manifestItems = sessionMI !== null ? normalizeManifestItems(sessionMI) : readManifestItems();
618
- if (manifestItems !== null && runFirstLoop) { // #269 — manifest preview is run-once
619
- const manifestRead = {
620
- op: "READ", suffix: "", signal: null, lineMarker: null,
621
- target: {
622
- kind: "url", raw: "plurnk:///manifest.json", scheme: "plurnk",
623
- username: null, password: null, hostname: null, port: null,
624
- pathname: "/manifest.json", params: {}, fragment: null,
625
- },
626
- body: manifestItems < 0 ? null : { dialect: "jsonpath", raw: `$[0:${manifestItems}]` },
627
- position: { line: 1, column: 1 },
628
- };
629
- await this.dispatch({
630
- statement: manifestRead, sessionId, runId, loopId, turnId,
631
- sequence: nextActionIndex, origin: "plurnk", onDispatch,
632
- });
633
- nextActionIndex++;
614
+ if (manifestItems !== null && runFirstLoop) { // #269 — catalog preview is run-once
615
+ // engine_scheme_catalog_summary is the scheme source: session-scoped, ordered,
616
+ // one row per scheme that has entries (scheme=null file). log:// is absent —
617
+ // it lives in log_entries, not the catalog (present-mode, the # Log section).
618
+ const catalogSchemes = await this.#db.engine_scheme_catalog_summary.all({ session_id: sessionId });
619
+ // known:/// + unknown:/// ALWAYS foist, even at zero entries — else the model
620
+ // burns a turn running FIND(known:///**) itself, assuming its memory is merely
621
+ // being withheld. Every other scheme keeps the with-entries default (an empty
622
+ // catalog foist is noise for schemes the model isn't expected to pre-populate).
623
+ const foistSchemes = [...catalogSchemes];
624
+ for (const always of ["known", "unknown"]) {
625
+ if (!foistSchemes.some((c) => c.scheme === always))
626
+ foistSchemes.push({ scheme: always, entries: 0 });
627
+ }
628
+ for (const { scheme, entries } of foistSchemes) {
629
+ const schemeName = scheme ?? "file";
630
+ // plurnk → its docs subtree (FIND(plurnk://docs/**), uncapped) — the self-
631
+ // documenting surface. The prompt is shown in # Prompt, so the plurnk catalog
632
+ // the model orients on IS the docs; doc links are no longer rendered inline (#270).
633
+ const isPlurnk = schemeName === "plurnk";
634
+ // entries===0 (an always-foisted empty known/unknown) → uncapped: nothing to
635
+ // clamp, and Math.min(items, 0)=0 would emit a degenerate <1,0> marker.
636
+ const cap = isPlurnk || manifestItems < 0 || entries === 0 ? null : Math.min(manifestItems, entries);
637
+ const catalogFind = {
638
+ op: "FIND", suffix: "", signal: null,
639
+ target: {
640
+ kind: "url",
641
+ raw: isPlurnk ? "plurnk://docs/**" : `${schemeName}:///**`,
642
+ scheme: schemeName,
643
+ username: null, password: null, hostname: null, port: null,
644
+ pathname: isPlurnk ? "/docs/**" : "/**",
645
+ params: {}, fragment: null,
646
+ },
647
+ body: null,
648
+ lineMarker: cap === null ? null : { marks: [1, cap] },
649
+ position: { line: 1, column: 1 },
650
+ };
651
+ await this.dispatch({
652
+ statement: catalogFind, sessionId, runId, loopId, turnId,
653
+ sequence: nextActionIndex, origin: "plurnk", onDispatch,
654
+ });
655
+ nextActionIndex++;
656
+ }
634
657
  }
635
658
  // #268 — auto-READ the configured AGENTS file(s) into THIS first model turn. Env-driven
636
659
  // (PLURNK_AGENTS_AUTO / PLURNK_AGENTS_FILES), overridable per-session by autoReadAgents; the
@@ -681,10 +704,13 @@ class Engine {
681
704
  nextActionIndex++;
682
705
  }
683
706
  }
684
- // §env-delta — pre-seed environment deltas (changes since this run last
685
- // reconciled) as system EDIT rows, before the packet composes; advance
686
- // the action index past them so model ops continue after.
707
+ // §environment-observation — pre-seed the run's ambient observations (what changed since
708
+ // it last looked) as foisted rows before the packet composes; advance the action index
709
+ // past them so model ops continue after. Two instances of one machine: env-delta (sibling
710
+ // edits · timestamp cursor · always folded) and exec streams (channel bytes · byte cursor ·
711
+ // terminal delta opens). §env-delta §exec-stream
687
712
  nextActionIndex += await this.#materializeEnvironmentDeltas({ sessionId, runId, loopId, turnId, fromSequence: nextActionIndex });
713
+ nextActionIndex += await this.#materializeStreamDeltas({ runId, loopId, turnId, fromSequence: nextActionIndex });
688
714
  // SPEC §telemetry — git working-tree state for the telemetry section, read once
689
715
  // (a service-side `git status` shell-out) and threaded into the budget
690
716
  // rebuild too so it isn't re-shelled on overflow.
@@ -947,11 +973,11 @@ class Engine {
947
973
  // requirements). Read Paths.defaultRequirements (PLURNK_REQUIREMENTS env →
948
974
  // requirements.md) fresh each build so edits take effect; a non-empty param wins.
949
975
  const baseRequirements = requirements.length > 0 ? requirements : await readFile(Paths.defaultRequirements, "utf8");
950
- // The op syntax leads the requirements. PLAN is mandated unconditionally by
951
- // plurnk.md §Imperatives (grammar 0.70 requires every turn to lead with PLAN),
952
- // so the service injects no separate plan directive here the former PLURNK_PLAN
953
- // gating is retired (PLURNK_PLAN is no longer a flag).
954
- const requirementsText = `Syntax: <<OPsuffix[signal]?(target)?<Line/Result>?:body?:OPsuffix\n\n${baseRequirements}`;
976
+ // No injected syntax line: the grammar already headlines the system definition (§Syntax) and
977
+ // leads requirements.md, so a third copy here was pure duplication in the model's packet. PLAN
978
+ // is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to
979
+ // lead with PLAN), so the service injects no separate plan directive either (the former
980
+ // PLURNK_PLAN gating is retired — PLURNK_PLAN is no longer a flag).
955
981
  const log = await this.#buildLog(runId);
956
982
  const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
957
983
  const countTokens = (t) => provider.countTokens(t); // §provider-surface-counttokens
@@ -965,9 +991,6 @@ class Engine {
965
991
  // omitted, section lines still shown). §tokenomics-render-weight-budget
966
992
  const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
967
993
  const budgetReadout = this.#renderBudget(PacketWire.measureLogBudget(log, countTokens), ceiling);
968
- // Per-scheme tally (§packet) so the model sees which schemes hold content without
969
- // probing e.g. FIND(known://**) every turn. "" when empty → the section is omitted.
970
- const catalogSummary = await this.#db.engine_scheme_catalog_summary.all({ session_id: sessionId });
971
994
  // The default packet: an ordered list of sections, each addressable state
972
995
  // (§packet-construction). `slot` is the prompt-cache boundary; the STATIC
973
996
  // sections (definition, tools) lead the system slot so they form the cached
@@ -981,13 +1004,12 @@ class Engine {
981
1004
  { name: "tools", slot: "system", header: null, content: tools.join("\n"), tokens: 0 }, // titleless — the examples flow on from plurnk.md (definition) directly above
982
1005
  { name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
983
1006
  ...(inject !== null ? [{ name: "inject", slot: "system", header: "Plurnk Operator Notes", content: inject, tokens: 0 }] : []),
984
- { name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log), tokens: 0 },
1007
+ { name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log, countTokens), tokens: 0 },
985
1008
  { name: "prompt", slot: "user", header: "Plurnk System User Prompt", content: prompt, tokens: 0 },
986
1009
  { name: "budget", slot: "user", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
987
1010
  { name: "errors", slot: "user", header: "Plurnk System Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
988
1011
  { name: "git", slot: "user", header: "Plurnk System Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
989
- { name: "catalog", slot: "user", header: "Plurnk System Catalog", content: PacketWire.renderCatalog(catalogSummary), tokens: 0 },
990
- { name: "requirements", slot: "user", header: "Plurnk System Requirements", content: requirementsText, tokens: 0 },
1012
+ { name: "requirements", slot: "user", header: "Plurnk System Requirements", content: baseRequirements, tokens: 0 },
991
1013
  ];
992
1014
  // Plugin packet control (§packet-construction): trusted schemes rewrite the
993
1015
  // default list — add, remove, reorder — in-process, before measurement.
@@ -1013,20 +1035,22 @@ class Engine {
1013
1035
  const packetTokens = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
1014
1036
  return { tokens: packetTokens, sections, telemetryErrors };
1015
1037
  }
1016
- // Budget readout body, rendered into the `# Plurnk System Budget` section.
1038
+ // Budget readout body, rendered into the `## Plurnk System Budget` section.
1017
1039
  // Headline `ceiling/free` only when a ceiling exists; section lines for the
1018
1040
  // curatable index/log weight the model can FOLD back. tokensFree is a
1019
1041
  // placeholder here — buildSystem substitutes it after measuring the packet.
1020
1042
  #renderBudget(log, ceiling) {
1021
1043
  const lines = [];
1022
1044
  if (ceiling !== null)
1023
- lines.push(`ceiling ${ceiling} · usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · free ${TOKENS_FREE_PLACEHOLDER}`);
1045
+ lines.push(`Token Ceiling ${ceiling} · Token Usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · Tokens Free ${TOKENS_FREE_PLACEHOLDER}`);
1024
1046
  if (log.entries > 0) {
1047
+ if (lines.length > 0)
1048
+ lines.push("");
1025
1049
  lines.push(`Log entries: ${log.entries} entries, ${log.tokens} tokens`);
1026
1050
  // Per-turn weight — the grinder's rollback unit, oldest first: the
1027
1051
  // model sees what's first to go (§tokenomics {§tokenomics-turn-totals}).
1028
1052
  if (log.byTurn.length > 0) {
1029
- lines.push("Turns:", "| turn | tokens |", "|---|--:|");
1053
+ lines.push("", "Turns:", "| turn | tokens |", "|---|--:|");
1030
1054
  for (const t of log.byTurn)
1031
1055
  lines.push(`| ${t.turn} | ${t.tokens} |`);
1032
1056
  }
@@ -1035,14 +1059,14 @@ class Engine {
1035
1059
  // lists log:/// rows (log items), distinct from catalog entries (plurnk.md: "EDIT
1036
1060
  // is only for entries. Do not attempt to edit log items.").
1037
1061
  if (log.largest.length > 0) {
1038
- lines.push("Heaviest items:", "| item | tokens |", "|---|--:|");
1062
+ lines.push("", "Heaviest items:", "| item | tokens |", "|---|--:|");
1039
1063
  for (const e of log.largest)
1040
1064
  lines.push(`| ${e.path} | ${e.tokens} |`);
1041
1065
  }
1042
1066
  }
1043
1067
  return lines.join("\n");
1044
1068
  }
1045
- // The # Plurnk System Tools capability sheet (SPEC §tools). A hook: each enabled
1069
+ // The ## Plurnk System Tools capability sheet (SPEC §tools). A hook: each enabled
1046
1070
  // capability contributes one line, rendered above Requirements so the model sees what
1047
1071
  // it can do before the rules. Each available executor tag contributes its self-documenting
1048
1072
  // example (plurnk-execs#7), retiring the blind EXEC.
@@ -1065,7 +1089,7 @@ class Engine {
1065
1089
  // the fuller doc (materialized at plurnk://docs/<tag>.md) rides an inline link whose
1066
1090
  // token cost lives on that manifest entry. No example → no line (like a provisional scheme).
1067
1091
  if (entry?.example)
1068
- tools.push(teachingLine(entry.example, tag, Boolean(entry.documentation)));
1092
+ tools.push(teachingLine(entry.example));
1069
1093
  }
1070
1094
  }
1071
1095
  return tools;
@@ -1216,6 +1240,7 @@ class Engine {
1216
1240
  mimetype_tx: r.mimetype_tx,
1217
1241
  folded: r.expanded === 0,
1218
1242
  source: r.source,
1243
+ attrs: r.attrs === null ? null : JSON.parse(r.attrs),
1219
1244
  }));
1220
1245
  }
1221
1246
  // §env-delta (§actor-boundary-no-mutex: runs share without locks; a conflict surfaces as a delta, never prevented) — at pre-turn build, surface what changed in the shared world since this
@@ -1256,6 +1281,37 @@ class Engine {
1256
1281
  }
1257
1282
  return written;
1258
1283
  }
1284
+ // §environment-observation — exec streams as an instance of the ambient-observe machine:
1285
+ // each turn, emit each owned channel's unshown byte-delta as a foisted READ@200 row. Folded
1286
+ // while the channel streams; the terminal delta (channel closed) auto-OPENs. The cursor is the
1287
+ // streamEnd recorded on the channel's prior delta — no exec-specific surfacing, just the
1288
+ // env-observe loop with a byte cursor where env-delta uses a timestamp. §exec-stream
1289
+ async #materializeStreamDeltas(args) {
1290
+ const { runId, loopId, turnId, fromSequence } = args;
1291
+ const channels = await this.#db.engine_run_stream_channels.all({ run_id: runId });
1292
+ let written = 0;
1293
+ for (const ch of channels) {
1294
+ const prior = await this.#db.engine_stream_cursor.get({
1295
+ run_id: runId, scheme: ch.runtime, pathname: ch.coord, fragment: ch.channel,
1296
+ });
1297
+ const cursor = prior !== undefined ? (JSON.parse(prior.attrs).streamEnd ?? 0) : 0;
1298
+ if (ch.content.length <= cursor)
1299
+ continue; // nothing new to show this turn
1300
+ const closed = ch.state === "closed" || ch.state === "errored";
1301
+ // startLine continues the line count across turns: a multi-turn stream's deltas number
1302
+ // into one sequence (lines N..M, then M+1..), not N independent "1:" restarts. §exec-stream
1303
+ const startLine = (ch.content.slice(0, cursor).match(/\n/g)?.length ?? 0) + 1;
1304
+ await this.#db.engine_insert_stream_delta.run({
1305
+ run_id: runId, loop_id: loopId, turn_id: turnId, sequence: fromSequence + written,
1306
+ scheme: ch.runtime, pathname: ch.coord, fragment: ch.channel,
1307
+ rx: JSON.stringify({ status: 200, content: ch.content.slice(cursor), mimetype: "text/stream", startLine }),
1308
+ attrs: JSON.stringify({ streamEnd: ch.content.length }),
1309
+ expanded: closed ? 1 : 0, // §exec-stream — terminal delta auto-OPENs; ongoing folds
1310
+ });
1311
+ written++;
1312
+ }
1313
+ return written;
1314
+ }
1259
1315
  // §env-delta — the filesystem as an actor. Ambient disk divergences detected at
1260
1316
  // pre-turn (git membership re-read) are logged as the plurnk run's source=file EDIT
1261
1317
  // "fictions": no op happened, but EDIT is the only grammar the model has for "your
@@ -1719,12 +1775,12 @@ class Engine {
1719
1775
  const target = statement.target;
1720
1776
  if (target === null)
1721
1777
  return { status: 400, error: "run:// fork requires a source run" };
1722
- const name = pathnameFromPath(target).replace(/^\/+/, "");
1778
+ const name = target.kind === "url" ? (target.hostname ?? "") : ""; // §run-scheme — run is the AUTHORITY (run://<name>), not the path
1723
1779
  let srcRunId = ctx.runId;
1724
1780
  if (name !== "" && name !== ".") {
1725
1781
  const row = await this.#db.run_resolve_by_name.get({ session_id: ctx.sessionId, name });
1726
1782
  if (row === undefined)
1727
- return { status: 404, error: `run:///${name} not found in this session` };
1783
+ return { status: 404, error: `run://${name} not found in this session` };
1728
1784
  srcRunId = row.id;
1729
1785
  }
1730
1786
  if (ctx.injectRun === undefined)
@@ -1818,12 +1874,12 @@ class Engine {
1818
1874
  // terminate — abort any run by address; whoever holds it may end it.
1819
1875
  // `.`/"" = self. cancelRun (→ Daemon.cancelDrain) aborts the run's signal
1820
1876
  // (its loop closes 499); an idle run is a no-op-200, a missing run 404.
1821
- const name = pathnameFromPath(path).replace(/^\/+/, "");
1877
+ const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §run-scheme — run is the AUTHORITY
1822
1878
  let runId = ctx.runId;
1823
1879
  if (name !== "" && name !== ".") {
1824
1880
  const row = await this.#db.run_resolve_by_name.get({ session_id: ctx.sessionId, name });
1825
1881
  if (row === undefined)
1826
- return { status: 404, error: `run:///${name} not found in this session` };
1882
+ return { status: 404, error: `run://${name} not found in this session` };
1827
1883
  runId = row.id;
1828
1884
  }
1829
1885
  if (this.#cancelRun === undefined)
@@ -1991,12 +2047,12 @@ class Engine {
1991
2047
  let attrsObj = (result.attrs !== undefined && result.attrs !== null)
1992
2048
  ? { ...result.attrs }
1993
2049
  : {};
1994
- // EXEC stream entry addresses by RUNTIME TAG as authority (§exec): it lives at
1995
- // <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2) the runtime tag
1996
- // is the scheme, the coordinate already unique per statement. The log row's target
1997
- // points at this same address; its log:/// coordinate shares the trailing
1998
- // <loop>/<turn>/<seq>, so the model correlates op to stream output. Runtime comes
1999
- // from statement.signal (EXEC's runtime slot) so it's resolvable for failed execs
2050
+ // EXEC produces a stream entry addressed by RUNTIME TAG as authority (§exec): it lives
2051
+ // at <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2). That address is a
2052
+ // SEPARATE `stream` link in attrs NOT an overload of `target`, which stays faithful to
2053
+ // the EXEC's own slot (the cwd, or the path to the executable). The log:/// coordinate
2054
+ // shares the trailing <loop>/<turn>/<seq>, so the op still correlates to its stream.
2055
+ // Runtime comes from statement.signal (EXEC's runtime slot), resolvable for failed execs
2000
2056
  // too; empty/absent = the default shell.
2001
2057
  if (statement.op === "EXEC") {
2002
2058
  const seqs = await this.#db.engine_loop_turn_seqs.get({
@@ -2006,9 +2062,8 @@ class Engine {
2006
2062
  throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
2007
2063
  const runtime = (typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
2008
2064
  const coordPathname = `/${seqs.loop_seq}/${seqs.turn_seq}/${sequence}`;
2009
- target.scheme = runtime;
2010
- target.pathname = coordPathname;
2011
2065
  attrsObj.pathname = coordPathname;
2066
+ attrsObj.stream = `${runtime}://${coordPathname}`;
2012
2067
  // Mutate the in-memory result.attrs too: the dispatch path
2013
2068
  // hands originalResult.attrs to handler.applyResolution after
2014
2069
  // proposal accept (see #acceptResolution). Both views — the
@@ -2070,8 +2125,10 @@ class Engine {
2070
2125
  // Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
2071
2126
  // it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
2072
2127
  // the entry (/prompt/<loop>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
2073
- // namespace: keep it in hostname.
2074
- const foldNs = scheme !== null && this.#schemes.has(scheme);
2128
+ // namespace: keep it in hostname. run:// is the one registered EXCEPTION — its authority IS the
2129
+ // run selector (§run-scheme), and run:/// (self) must stay distinct from run://name, so Run.ts
2130
+ // folds the owner into the storage path itself, never here.
2131
+ const foldNs = scheme !== null && scheme !== "run" && this.#schemes.has(scheme);
2075
2132
  return {
2076
2133
  scheme, username: path.username, password: path.password,
2077
2134
  hostname: foldNs ? null : path.hostname, port: path.port,