@plurnk/plurnk-service 0.50.0 → 0.52.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 (48) hide show
  1. package/.env.example +2 -2
  2. package/SPEC.md +15 -5
  3. package/dist/core/Engine.d.ts.map +1 -1
  4. package/dist/core/Engine.js +73 -54
  5. package/dist/core/Engine.js.map +1 -1
  6. package/dist/core/Engine.sql +4 -6
  7. package/dist/core/git-membership.js +1 -1
  8. package/dist/core/git-membership.js.map +1 -1
  9. package/dist/core/run-ops.sql +5 -0
  10. package/dist/core/session-settings.d.ts +4 -0
  11. package/dist/core/session-settings.d.ts.map +1 -1
  12. package/dist/core/session-settings.js +8 -0
  13. package/dist/core/session-settings.js.map +1 -1
  14. package/dist/schemes/Plurnk.js +1 -1
  15. package/dist/schemes/Plurnk.js.map +1 -1
  16. package/dist/schemes/Run.d.ts +4 -2
  17. package/dist/schemes/Run.d.ts.map +1 -1
  18. package/dist/schemes/Run.js +86 -51
  19. package/dist/schemes/Run.js.map +1 -1
  20. package/dist/schemes/_entry-crud.sql +12 -0
  21. package/dist/schemes/_entry-find.d.ts +3 -17
  22. package/dist/schemes/_entry-find.d.ts.map +1 -1
  23. package/dist/schemes/_entry-find.js +67 -65
  24. package/dist/schemes/_entry-find.js.map +1 -1
  25. package/dist/schemes/_entry-graph.d.ts +0 -6
  26. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  27. package/dist/schemes/_entry-graph.js +0 -8
  28. package/dist/schemes/_entry-graph.js.map +1 -1
  29. package/dist/schemes/_entry-graph.sql +0 -10
  30. package/dist/schemes/_entry-manifest.d.ts +13 -2
  31. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  32. package/dist/schemes/_entry-manifest.js +84 -89
  33. package/dist/schemes/_entry-manifest.js.map +1 -1
  34. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  35. package/dist/schemes/_entry-ops.js +14 -6
  36. package/dist/schemes/_entry-ops.js.map +1 -1
  37. package/dist/schemes/_entry-ops.sql +11 -0
  38. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  39. package/dist/schemes/_entry-semantic.js +6 -5
  40. package/dist/schemes/_entry-semantic.js.map +1 -1
  41. package/dist/server/envelope.d.ts.map +1 -1
  42. package/dist/server/envelope.js +9 -0
  43. package/dist/server/envelope.js.map +1 -1
  44. package/dist/server/methods/session_create.js +4 -4
  45. package/dist/server/methods/session_create.js.map +1 -1
  46. package/migrations/0000-00-00.01_schema.sql +3 -1
  47. package/package.json +4 -4
  48. package/requirements.md +11 -3
package/.env.example CHANGED
@@ -124,8 +124,8 @@ PLURNK_MANIFEST_ITEMS=-1
124
124
  # Prompt-preview cap: the loop's prompt renders in user.prompt every turn, and a fat prompt
125
125
  # replays each turn (bloat). Show the first N CHARS of the body + a pointer to the full
126
126
  # prompt (always READable at its plurnk://prompt/<loop>/<seq> entry — nothing is lost).
127
- # -1 = no cap (render the full prompt every turn). Default 512.
128
- PLURNK_PROMPT_PREVIEW_CHARS=512
127
+ # -1 = no cap (render the full prompt every turn).
128
+ PLURNK_PROMPT_PREVIEW_CHARS=1024
129
129
 
130
130
  # Session-tier ceiling on CONCURRENT active runs (a run with a non-terminal loop)
131
131
  # — the fork-bomb / destabilization brake. -1 = no cap (default); only concurrency
package/SPEC.md CHANGED
@@ -146,13 +146,13 @@ 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
 
153
153
  **Question.** §actor-boundary isolates runs and lets the runtime self-host, but it stands on an ownership model it never states: what does a *session* own versus a *run*; what is shared versus private; and what does a fork carry? Unstated, the downstream questions — which run `log.read` reads, what a fork copies, where a per-client window onto the workspace would live — grow subtle, then metastasize. Drawn once, they vanish.
154
154
 
155
- **Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk:///manifest.json` (§packet) — under one membership overlay (§membership). Exactly one filesystem and one overlay per session; neither is per-run. A **run** is a process whose entire private memory is its **log** (§lifecycle-terms) — its loops, turns, and rows, each row carrying its own content, attribution (`origin`/`source`, §env-delta), and fold-state (`expanded`). A run owns **no entries** and **no membership**; even its visibility is not a possession but a bit on its own rows. It is a *history over the shared world, not a world*.
155
+ **Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk:///manifest.json` (§packet) — under one membership overlay (§membership). Exactly one filesystem and one overlay per session; neither is per-run. A **run** is a process whose private memory is its **log** (§lifecycle-terms) — its loops, turns, and rows, each row carrying its own content, attribution (`origin`/`source`, §env-delta), and fold-state (`expanded`). A run owns **no membership**; even its visibility is not a possession but a bit on its own rows. It is a *history over the shared world, not a world*.
156
156
 
157
157
  **One filesystem.** The entries are the session's: `entries.session_id`, never a run. A write by any run is a write to the one filesystem every run reads; there is no per-run entry set. {§machine-processes-one-filesystem}
158
158
 
@@ -261,6 +261,16 @@ First path segment = provider plugin; rest = provider's own model id.
261
261
 
262
262
  Author-facing contract: [plurnk-schemes#1](https://github.com/plurnk/plurnk-schemes/issues/1). Below: what plurnk-service exposes to schemes and orchestrates over them.
263
263
 
264
+ ### §scheme-address Address resolution (RFC 3986)
265
+
266
+ Every op targets a URI; the entry key is `(scope, scheme, pathname)`. The URI parses per RFC 3986 (`scheme://[authority]/path`) and maps to that key by one rule — no per-scheme carve-out:
267
+
268
+ - A **registered** scheme is a plurnk namespace: its authority is a leading path segment, folded into the pathname (`Engine.#extractTarget` → `foldAuthorityIntoPath`). So `known://x`, `known:///x`, and pathname `/x` are the same entry — the authority is never a host, and the two-slash and three-slash forms are not distinct resources. {§scheme-address-namespace-fold}
269
+ - A **foreign** scheme (unregistered — `http`/`https`) is a real web host: its authority stays in `hostname`, never folded.
270
+ - `file` persists `scheme = NULL`; a relative path resolves against the workspace root to the namespace-absolute `/rel` key (RFC §5 reference resolution), and a path escaping the root is 403 (§membership).
271
+
272
+ Storage keys on the resolved `(scheme, pathname)` **verbatim** — the leading slash is the namespace origin, not a filesystem absolute, and is never re-normalized at the storage boundary.
273
+
264
274
  ### §scheme-manifest Manifest
265
275
 
266
276
  Per author contract. Each scheme declares a `static manifest: SchemeManifest` with `name`, `channels`, `defaultChannel`, `category`, `scope`, `writableBy`, `volatile`, `modelVisible`, optional `flags`. {§scheme-manifest-manifest} Identity match enforced at plugin load: `manifest.name` must equal `package.json#plurnk.name`.
@@ -546,7 +556,7 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
546
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}
547
557
  - `signal` is a tag filter; entries match if they have ALL listed tags. {§find-tag-filter-and-semantics}
548
558
  - Session + scheme scoped — no cross-session/cross-scheme leakage. {§find-scoped-isolation}
549
- - 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}
550
560
 
551
561
  ### §send SEND
552
562
 
@@ -1192,7 +1202,7 @@ The wire projection (`PacketWire.renderSlot`) groups sections by slot into the s
1192
1202
 
1193
1203
  **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}
1194
1204
 
1195
- **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}
1205
+ **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}
1196
1206
 
1197
1207
  ### §telemetry user.telemetry — model-facing runtime telemetry
1198
1208
 
@@ -1230,7 +1240,7 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
1230
1240
 
1231
1241
  ### §tools user.tools — the capability sheet
1232
1242
 
1233
- A `# Plurnk System Tools` section renders **above** `# Plurnk System Requirements` a hook-populated list of the capabilities enabled this session, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the whole section is omitted when nothing is enabled. {§tools-capability-sheet}
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}
1234
1244
 
1235
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]…`.
1236
1246
 
@@ -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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"}
@@ -498,6 +498,13 @@ class Engine {
498
498
  // writes consume low indices; model ops continue from there.
499
499
  const seqRow = await this.#db.engine_next_turn_sequence.get({ loop_id: loopId });
500
500
  const seq = seqRow.next;
501
+ // #269 — loops.sequence is the loop's ordinal WITHIN the run. Turn-0 foists that belong to the
502
+ // RUN (manifest preview, AGENTS, operator docs) gate on the run's FIRST loop, not every loop's
503
+ // first turn; per-loop foists (the prompt, @file) still fire each loop. Read once, turn-1 only.
504
+ const loopRow = seq === 1
505
+ ? await this.#db.engine_get_loop_prompt.get({ loop_id: loopId })
506
+ : undefined;
507
+ const runFirstLoop = (loopRow?.sequence ?? 0) === 1;
501
508
  const openRow = await this.#db.engine_open_turn.get({
502
509
  loop_id: loopId, sequence: seq,
503
510
  });
@@ -525,7 +532,8 @@ class Engine {
525
532
  // #231 — env docs (PLURNK_MD_*) UNION the session's client docs; foist a READ of
526
533
  // each materialized plurnk:///<alias>.md (loop_run materialized the same set).
527
534
  const { mdDocs } = await SessionSettings.read(this.#db, sessionId);
528
- for (const doc of await SessionSettings.resolveDocs(mdDocs)) {
535
+ // #269 operator docs are run-once; foist them only on the run's first loop.
536
+ for (const doc of runFirstLoop ? await SessionSettings.resolveDocs(mdDocs) : []) {
529
537
  const docTarget = {
530
538
  kind: "url", raw: `plurnk:///${doc.entryName}`, scheme: "plurnk",
531
539
  username: null, password: null, hostname: null, port: null,
@@ -541,7 +549,7 @@ class Engine {
541
549
  });
542
550
  nextActionIndex++;
543
551
  }
544
- const promptRow = await this.#db.engine_get_loop_prompt.get({ loop_id: loopId });
552
+ const promptRow = loopRow; // #269 — already read above (per-loop; fires every loop's turn 1)
545
553
  if (promptRow !== undefined && typeof promptRow.prompt === "string" && promptRow.prompt.length > 0) {
546
554
  const promptPath = {
547
555
  kind: "url", raw: `plurnk://prompt/${loopId}/${seq}`,
@@ -568,12 +576,11 @@ class Engine {
568
576
  nextActionIndex++;
569
577
  }
570
578
  }
571
- // plurnk:///manifest.json rewritten EVERY turn (a live view of the
572
- // entry set, which changes each turn). A derived view (computed each
573
- // turn), NOT an action written directly (Engine.inject's path): no log entry,
574
- // no sequence slot, not dispatched. The catalog body is built in the
575
- // schemes layer (_entry-manifest); the engine only orchestrates the
576
- // 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.
577
584
  const systemCtx = {
578
585
  db: this.#db, sessionId, runId, loopId, turnId,
579
586
  writer: "plurnk",
@@ -589,55 +596,64 @@ class Engine {
589
596
  // prompt-composition (EMI is eager + exhaustive — git is the only bound). When the
590
597
  // session's project_root is a git working tree, tracked files are
591
598
  // members without a client `add`; active members are materialized
592
- // (disk → body channel) so they appear in the manifest below. No-ops
593
- // 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
594
601
  // this turn's packet reflects them.
595
602
  const fsDivergences = await GitMembership.indexGitMembership(systemCtx);
596
603
  await this.#logFsFictions(sessionId, fsDivergences);
597
- await EntryCrud.writeEntry("/manifest.json", {
598
- channels: { body: { content: await EntryManifest.buildManifestBody(systemCtx), mimetype: "application/json" } },
599
- tags: [],
600
- }, systemCtx, "plurnk");
601
- // Manifest preview (PLURNK_MANIFEST_ITEMS, §actor-boundary-manifest-preview):
602
- // a turn-0 foisted READ of the just-built catalog so a run opens with what's
603
- // available, not blank. -1 → full; N → the first N items (jsonpath slice — the
604
- // manifest is JSON); off by default. AFTER the manifest write so the READ hits
605
- // 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.
606
610
  if (seq === 1) {
607
611
  // #231 — a session's client-chosen manifestItems REPLACES the env default outright.
608
612
  const { manifestItems: sessionMI, autoReadAgents } = await SessionSettings.read(this.#db, sessionId);
609
613
  const manifestItems = sessionMI !== null ? normalizeManifestItems(sessionMI) : readManifestItems();
610
- if (manifestItems !== null) {
611
- const manifestRead = {
612
- op: "READ", suffix: "", signal: null, lineMarker: null,
613
- target: {
614
- kind: "url", raw: "plurnk:///manifest.json", scheme: "plurnk",
615
- username: null, password: null, hostname: null, port: null,
616
- pathname: "/manifest.json", params: {}, fragment: null,
617
- },
618
- body: manifestItems < 0 ? null : { dialect: "jsonpath", raw: `$[0:${manifestItems}]` },
619
- position: { line: 1, column: 1 },
620
- };
621
- await this.dispatch({
622
- statement: manifestRead, sessionId, runId, loopId, turnId,
623
- sequence: nextActionIndex, origin: "plurnk", onDispatch,
624
- });
625
- 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
+ for (const { scheme, entries } of catalogSchemes) {
620
+ const schemeName = scheme ?? "file";
621
+ const cap = manifestItems < 0 ? null : Math.min(manifestItems, entries);
622
+ const catalogFind = {
623
+ op: "FIND", suffix: "", signal: null,
624
+ target: {
625
+ kind: "url", raw: `${schemeName}:///**`, scheme: schemeName,
626
+ username: null, password: null, hostname: null, port: null,
627
+ pathname: "", params: {}, fragment: null,
628
+ },
629
+ body: null,
630
+ lineMarker: cap === null ? null : { marks: [1, cap] },
631
+ position: { line: 1, column: 1 },
632
+ };
633
+ await this.dispatch({
634
+ statement: catalogFind, sessionId, runId, loopId, turnId,
635
+ sequence: nextActionIndex, origin: "plurnk", onDispatch,
636
+ });
637
+ nextActionIndex++;
638
+ }
626
639
  }
627
- // #250 — auto-READ the project's AGENTS.md scratchpad into THIS first model turn
628
- // when the session opted in AND it's a member. The client picks it (gitignored by
629
- // convention not a git member); the engine READs it here so its body is part of
630
- // turn-1's log — a normal file:/// member READ (the model sees only the READ; it
631
- // stays read-write, so the model edits the scratchpad back as it evolves).
632
- if (autoReadAgents === true) {
633
- const agentsMember = await this.#db.crud_get_member_sig.get({ session_id: sessionId, scheme: null, pathname: "/AGENTS.md" });
634
- if (agentsMember !== undefined) {
640
+ // #268 — auto-READ the configured AGENTS file(s) into THIS first model turn. Env-driven
641
+ // (PLURNK_AGENTS_AUTO / PLURNK_AGENTS_FILES), overridable per-session by autoReadAgents; the
642
+ // matching files are auto-PICKed into membership at session setup (envelope). The model sees
643
+ // only the READ — a normal file:/// member READ, read-write so it edits the scratchpad back.
644
+ const { auto: agentsAuto, files: agentsFiles } = SessionSettings.resolveAgentsAutoload(autoReadAgents);
645
+ if (agentsAuto && runFirstLoop) { // #269 — run-once, the run's first loop
646
+ for (const file of agentsFiles) {
647
+ const pathname = file.startsWith("/") ? file : `/${file}`;
648
+ const member = await this.#db.crud_get_member_sig.get({ session_id: sessionId, scheme: null, pathname });
649
+ if (member === undefined)
650
+ continue; // absent / non-git session → not a member → skip
635
651
  const agentsRead = {
636
652
  op: "READ", suffix: "", signal: null, lineMarker: null,
637
653
  target: {
638
- kind: "url", raw: "file:///AGENTS.md", scheme: "file",
654
+ kind: "url", raw: `file://${pathname}`, scheme: "file",
639
655
  username: null, password: null, hostname: null, port: null,
640
- pathname: "/AGENTS.md", params: {}, fragment: null,
656
+ pathname, params: {}, fragment: null,
641
657
  },
642
658
  body: null, position: { line: 1, column: 1 },
643
659
  };
@@ -967,7 +983,7 @@ class Engine {
967
983
  const inject = await readPacketInject(); // #240 — operator section, per-turn, fail-hard on a broken path
968
984
  const defaults = [
969
985
  { name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
970
- { name: "tools", slot: "system", header: "Plurnk System Tools", content: tools.join("\n"), tokens: 0 },
986
+ { name: "tools", slot: "system", header: null, content: tools.join("\n"), tokens: 0 }, // titleless — the examples flow on from plurnk.md (definition) directly above
971
987
  { name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
972
988
  ...(inject !== null ? [{ name: "inject", slot: "system", header: "Plurnk Operator Notes", content: inject, tokens: 0 }] : []),
973
989
  { name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log), tokens: 0 },
@@ -1708,12 +1724,12 @@ class Engine {
1708
1724
  const target = statement.target;
1709
1725
  if (target === null)
1710
1726
  return { status: 400, error: "run:// fork requires a source run" };
1711
- const name = pathnameFromPath(target).replace(/^\/+/, "");
1727
+ const name = target.kind === "url" ? (target.hostname ?? "") : ""; // §run-scheme — run is the AUTHORITY (run://<name>), not the path
1712
1728
  let srcRunId = ctx.runId;
1713
1729
  if (name !== "" && name !== ".") {
1714
1730
  const row = await this.#db.run_resolve_by_name.get({ session_id: ctx.sessionId, name });
1715
1731
  if (row === undefined)
1716
- return { status: 404, error: `run:///${name} not found in this session` };
1732
+ return { status: 404, error: `run://${name} not found in this session` };
1717
1733
  srcRunId = row.id;
1718
1734
  }
1719
1735
  if (ctx.injectRun === undefined)
@@ -1807,12 +1823,12 @@ class Engine {
1807
1823
  // terminate — abort any run by address; whoever holds it may end it.
1808
1824
  // `.`/"" = self. cancelRun (→ Daemon.cancelDrain) aborts the run's signal
1809
1825
  // (its loop closes 499); an idle run is a no-op-200, a missing run 404.
1810
- const name = pathnameFromPath(path).replace(/^\/+/, "");
1826
+ const name = path.kind === "url" ? (path.hostname ?? "") : ""; // §run-scheme — run is the AUTHORITY
1811
1827
  let runId = ctx.runId;
1812
1828
  if (name !== "" && name !== ".") {
1813
1829
  const row = await this.#db.run_resolve_by_name.get({ session_id: ctx.sessionId, name });
1814
1830
  if (row === undefined)
1815
- return { status: 404, error: `run:///${name} not found in this session` };
1831
+ return { status: 404, error: `run://${name} not found in this session` };
1816
1832
  runId = row.id;
1817
1833
  }
1818
1834
  if (this.#cancelRun === undefined)
@@ -2056,10 +2072,13 @@ class Engine {
2056
2072
  if (path.kind === "local")
2057
2073
  return { scheme: null, username: null, password: null, hostname: null, port: null, pathname: decodePathParens(path.raw), params: null, fragment: null }; // #239 item 4
2058
2074
  const scheme = path.scheme === "file" ? null : path.scheme;
2059
- // plurnk uses its authority as a namespace — fold it into the canonical pathname so the
2060
- // log keys identically to the entry (/prompt/<loop>, /docs/x.md). A web host (http://) is
2061
- // NOT a namespace: keep it in hostname.
2062
- const foldNs = scheme === "plurnk";
2075
+ // Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
2076
+ // it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
2077
+ // the entry (/prompt/<loop>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
2078
+ // namespace: keep it in hostname. run:// is the one registered EXCEPTION — its authority IS the
2079
+ // run selector (§run-scheme), and run:/// (self) must stay distinct from run://name, so Run.ts
2080
+ // folds the owner into the storage path itself, never here.
2081
+ const foldNs = scheme !== null && scheme !== "run" && this.#schemes.has(scheme);
2063
2082
  return {
2064
2083
  scheme, username: path.username, password: path.password,
2065
2084
  hostname: foldNs ? null : path.hostname, port: path.port,