@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.
- package/SPEC.md +17 -11
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +124 -67
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +41 -11
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +1 -2
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/git-membership.js +1 -1
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/packet-wire.d.ts +1 -2
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +111 -138
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/run-ops.sql +5 -0
- package/dist/core/teaching.d.ts +1 -1
- package/dist/core/teaching.d.ts.map +1 -1
- package/dist/core/teaching.js +6 -4
- package/dist/core/teaching.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +11 -24
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +3 -2
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.js +1 -1
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Run.d.ts +4 -2
- package/dist/schemes/Run.d.ts.map +1 -1
- package/dist/schemes/Run.js +86 -51
- package/dist/schemes/Run.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +12 -0
- package/dist/schemes/_entry-find.d.ts +4 -17
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +74 -66
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-graph.d.ts +0 -6
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +0 -8
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +0 -10
- package/dist/schemes/_entry-manifest.d.ts +13 -2
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +84 -89
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +14 -6
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +11 -0
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +5 -8
- package/dist/server/Daemon.js.map +1 -1
- package/docs/log.md +1 -1
- package/migrations/0000-00-00.01_schema.sql +3 -1
- package/package.json +16 -15
- package/requirements.md +5 -3
- package/dist/schemes/exec-receipt.d.ts +0 -4
- package/dist/schemes/exec-receipt.d.ts.map +0 -1
- package/dist/schemes/exec-receipt.js +0 -25
- 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
|
-
**
|
|
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:
|
|
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
|
|
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-band — like 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; // "
|
|
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.**
|
|
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
|
|
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**
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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":"AACA,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.js
CHANGED
|
@@ -576,12 +576,11 @@ class Engine {
|
|
|
576
576
|
nextActionIndex++;
|
|
577
577
|
}
|
|
578
578
|
}
|
|
579
|
-
//
|
|
580
|
-
// entry
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
//
|
|
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
|
|
601
|
-
// on headless / non-git sessions. Runs BEFORE the
|
|
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
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
//
|
|
610
|
-
//
|
|
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 —
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
})
|
|
633
|
-
|
|
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
|
-
// §
|
|
685
|
-
//
|
|
686
|
-
//
|
|
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
|
-
//
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
//
|
|
954
|
-
|
|
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: "
|
|
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
|
|
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(`
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
1995
|
-
// <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2)
|
|
1996
|
-
//
|
|
1997
|
-
//
|
|
1998
|
-
// <loop>/<turn>/<seq>, so the
|
|
1999
|
-
// from statement.signal (EXEC's runtime slot)
|
|
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
|
-
|
|
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,
|