@plurnk/plurnk-service 0.39.0 → 0.41.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/.env.example ADDED
@@ -0,0 +1,164 @@
1
+ # plurnk-service environment cascade.
2
+ #
3
+ # Override order: `.env.example` < `.env` < `.env.<profile>` < shell < params.
4
+ # Feature-flag bools use `=== "1"` exactly. Never `=== "true"`.
5
+
6
+ # --- Storage ---
7
+ PLURNK_DB_PATH=./plurnk.db
8
+
9
+ # --- Daemon transport ---
10
+ PLURNK_HOST=127.0.0.1
11
+ PLURNK_PORT=3044
12
+
13
+ # --- Model aliases ---
14
+ # PLURNK_MODEL is the active provider for every loop (a client may override per
15
+ # loop via loop.run({alias})). Out of the box it is `plurnk` — the zero-friction
16
+ # default: Plurnk, Inc.'s hosted, grammar-tuned model at model.plurnk.ai, served
17
+ # anonymously with no account on a throttled free tier. Change this one line to
18
+ # use another model; set it empty for no default provider. Prompts on the default
19
+ # route to model.plurnk.ai — see "Plurnk account" below for (optional) creds.
20
+ PLURNK_MODEL=plurnk
21
+
22
+ PLURNK_MODEL_plurnk="plurnk/plurnk"
23
+
24
+ # --- Plurnk account (OPTIONAL) ---
25
+ # Plurnk Service is vendor-agnostic, and connects to (almost) any LLM.
26
+ # Plurnk Service is MIT Licensed and owned by Plurnk Foundation, not Plurnk, Inc.
27
+ # Plurnk, Inc.'s grammar-tuned plurnk.ai model is offered as an optional convenience.
28
+ # You may (OPTIONALLY) obtain free PLURNK_ACCOUNT and PLURNK_KEY variables at https://plurnk.ai.
29
+
30
+ # PLURNK_ACCOUNT="..."
31
+ # PLURNK_KEY="..."
32
+ # The hosted endpoint defaults to https://model.plurnk.ai/v1; override it to self-
33
+ # host the model or point at a mirror — it speaks the llama-server API (same grammar
34
+ # transport) like the local model.
35
+ # PLURNK_BASE_URL="https://model.plurnk.ai/v1"
36
+
37
+ # --- Loop control ---
38
+ # Operator turn ceiling. -1 (default) = no cap (loops end via SEND, budget,
39
+ # strikes, or cycle detection). A positive value is an inviolable hard cap a
40
+ # per-call loop.run({maxTurns}) cannot exceed (min wins).
41
+ PLURNK_MAX_TURNS=-1
42
+ # Per-emission op cap (runaway-loop guard). A client may tighten it per session via
43
+ # session.create settings.maxCommands (min wins); never raise it past this (#232).
44
+ PLURNK_MAX_COMMANDS=99
45
+ PLURNK_RPC_TIMEOUT=30000
46
+ # Post-EXEC breath: after a turn fires a non-inline EXEC whose spawn is still in
47
+ # flight at the turn boundary, wait this many ms before assembling the next packet
48
+ # so a fast exec's output can land in it instead of a turn later. A fixed grace
49
+ # beat, NOT a wait-for-completion (slow execs proceed + surface via the wake path).
50
+ # 0 = off (the model sees fast-exec output a turn late, as today).
51
+ PLURNK_EXEC_WAIT_MS=0
52
+ PLURNK_LOOP_TIMEOUT=86400000
53
+
54
+ # --- Engine rails ---
55
+ PLURNK_MAX_STRIKES=3
56
+ PLURNK_MIN_CYCLES=3
57
+ PLURNK_MAX_CYCLE_PERIOD=4
58
+
59
+ # --- Schemes ---
60
+ # (Workspace root for file ops is per-session — supplied by the client
61
+ # via session.create({projectRoot}) or session.set_root({projectRoot});
62
+ # stored on sessions.project_root. No env-level default.)
63
+
64
+ # --- Git integration ---
65
+ # PLURNK_GIT_ALLOWED — hard ceiling for git membership + integration (repo telemetry,
66
+ # EXEC[git]). =1 permits it (then declared repos + per-session config decide; a client
67
+ # may deny its own session via session.create settings.git:false, #232); =0 flatly
68
+ # denies it service-wide, un-re-enableable (the sandbox/benchmark lockout).
69
+ PLURNK_GIT_ALLOWED=1
70
+ # PLURNK_GIT_AUTO — default repo declaration. =1 auto-declares an implicit `repo` at
71
+ # project_root (no-op when it isn't a git tree); =0 declares nothing — clients add
72
+ # repos explicitly via the `repo` overlay. SPEC §membership forest.
73
+ PLURNK_GIT_AUTO=1
74
+
75
+ # --- Reference docs (auto-READ at turn 0) ---
76
+ # PLURNK_MD_<ALIAS>=<path> materializes <path>'s markdown as a plurnk://<ALIAS>.md
77
+ # entry the model READs at turn 0 — an idiomatic way to inject standing context
78
+ # (an ordinary entry + READ op, not a bespoke packet section). ~ expands to home;
79
+ # relative paths resolve against the package root. A client may add its own docs
80
+ # per session via session.create settings.mdDocs (content, not a path); those UNION
81
+ # with these env docs, keyed by alias — the client wins a collision (#231).
82
+ # Commented out = no docs by default.
83
+ # PLURNK_MD_POLICY=~/.plurnk/AGENTS.md
84
+
85
+ # Turn-0 manifest preview: foist a READ of plurnk://manifest.json into the model's
86
+ # first turn so a run opens with the session catalog, not blank. -1 = the full
87
+ # manifest; N = the first N items (jsonpath slice); 0 = off. The service sets this
88
+ # servicewide default; a client may override it per session via
89
+ # session.create settings.manifestItems, which replaces this value (#231).
90
+ PLURNK_MANIFEST_ITEMS=-1
91
+
92
+ # Session-tier ceiling on CONCURRENT active runs (a run with a non-terminal loop)
93
+ # — the fork-bomb / destabilization brake. -1 = no cap (default); only concurrency
94
+ # is bounded, never lifetime, since sessions persist for months. A spawn/fork past
95
+ # the ceiling fails hard (508 — no queue, no retry); the acting run counts itself.
96
+ PLURNK_SESSION_RUNS_MAX_ACTIVE=-1
97
+
98
+ # --- Providers (universal knobs) ---
99
+ # Reasoning budget (providers 0.2.5+): the single provider reasoning gate. REQUIRED,
100
+ # fail-hard if unset — 0 = native reasoning off (in-DSL PLAN does the reasoning), -1 =
101
+ # adaptive / no cap, N = capped at N tokens. Provider modules translate it per model
102
+ # family (reasoning_effort tiers, budget_tokens). Floor (gemma): 0.
103
+ PLURNK_PROVIDERS_REASONING_BUDGET=0
104
+ #
105
+ # Planning: enable the grammar's <<PLAN:...:PLAN op so the model reasons in-band before
106
+ # acting. A shared universal knob (like PLURNK_FETCH_TIMEOUT below): the service advertises
107
+ # the op in # Plurnk System Tools; the provider reads it too. =1 on, =0 off.
108
+ PLURNK_PLAN=1
109
+ # Service-wide fetch timeout in ms — the universal upper bound on any
110
+ # single outbound request (provider calls, future http:// scheme reads,
111
+ # anything that does network IO). Streamed completions can run long;
112
+ # default 600000 (10 minutes). Provider/scheme modules MAY have their
113
+ # own vendor-specific overrides for narrower defaults, but they honor
114
+ # this as the operator's universal ceiling.
115
+ PLURNK_FETCH_TIMEOUT=600000
116
+ # PLURNK_VERSION_POLL_TTL — how long (ms) discover caches its npm-registry version poll
117
+ # (service + client `latest`) before a background refresh. Best-effort; the poll never
118
+ # blocks discover, and offline/registry-down omits `latest`. ~hourly is plenty. #235
119
+ PLURNK_VERSION_POLL_TTL=3600000
120
+ # Grammar-constrained sampling: SELECTS the GBNF variant the provider constrains
121
+ # every generate() to, so capable backends (llama-server, detected via the
122
+ # /v1/models fingerprint) can only sample valid plurnk DSL; others drop it silently.
123
+ # A bare name (plurnk.gbnf) is a variant shipped by
124
+ # @plurnk/plurnk-grammar; an absolute/relative path is your own. =0 (or empty) disables.
125
+ PLURNK_PROVIDERS_GBNF=plurnk.gbnf
126
+ # Provider retry attempts (providers 0.7+): how many times generate() retries a
127
+ # TRANSIENT failure (429 rate-limit, 5xx/network) with exponential backoff (base 2s
128
+ # is a provider constant — the COUNT is the operator knob). REQUIRED, fail-hard if
129
+ # unset. Terminal errors (auth, quota, model refusal) never retry. 0 = no retries.
130
+ PLURNK_PROVIDER_RETRY_ATTEMPTS=3
131
+ # Override the model's reported context window in tokens. Provider modules
132
+ # normally read this from the model/API; set this to force a specific
133
+ # value (smaller for testing, larger for models that under-report). Unset
134
+ # = trust the provider's default.
135
+ # PLURNK_PROVIDER_CONTEXT_SIZE=
136
+
137
+ # --- Plugins (third-party trust) ---
138
+ # Trust gate over scope-agnostic plugin discovery across the four families
139
+ # (schemes, mimetypes, providers, execs): installed = discoverable, but this
140
+ # decides installed = TRUSTED — once, at the host, not re-answered per family.
141
+ # =0 / empty / unset → OFF: every installed plugin loads (today's behavior, no
142
+ # regression). A value → ON: @plurnk/* is always trusted, plus a comma-separated
143
+ # allowlist of additionally-trusted packages (anything else discovered is skipped
144
+ # with a telemetry note, never a crash). Use 1 (a value naming no real package)
145
+ # for "on, zero third-party". Example: =acme-execs-cobol,@firewolf/firepad
146
+ PLURNK_PLUGINS_TRUSTED_ONLY=0
147
+
148
+ # --- Semantic search (~query chunking) ---
149
+ # Project Semantics tiles each entry into <=window chunks so a large body is fully
150
+ # searchable, not truncated. ACTIVE only when the installed embedder reports its
151
+ # tokenizer; otherwise one whole-entry chunk (today's behavior). Both knobs are read
152
+ # only when chunking is active.
153
+ # Chunk budget in TOKENS. EMPTY (the default) = the installed embedder's reported
154
+ # window — NO model-specific number is assumed, so it scales to whatever embedder you
155
+ # install. Set a positive value ONLY to cap below the window (e.g. to sweep
156
+ # granularity); it is clamped to the window either way.
157
+ PLURNK_SEMANTIC_CHUNK_TOKENS=
158
+ # Overlap fraction [0,1): trailing context re-covered at each chunk boundary so a
159
+ # concept split across a cut still matches in both. ~0.15 is a standard RAG default.
160
+ PLURNK_SEMANTIC_CHUNK_OVERLAP=0.15
161
+
162
+ # --- Diagnostics ---
163
+ PLURNK_DEBUG=0
164
+ PLURNK_LOG_LEVEL=info
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # plurnk-service
2
2
 
3
3
  LLM agent runtime engine. Consumes [plurnk-grammar](https://github.com/plurnk/plurnk-grammar); exposes WebSocket JSON-RPC for clients. User-facing CLI: [plurnk](https://github.com/plurnk/plurnk).
4
+ Default Provider: [plurnk.ai](https://plurnk.ai).
5
+
6
+ * Plurnk Service is vendor-agnostic, and connects to (almost) any LLM.
7
+ * Plurnk Service is MIT Licensed and owned by Plurnk Foundation, not Plurnk, Inc.
8
+ * Plurnk, Inc.'s grammar-tuned [plurnk.ai](https://plurnk.ai) model is offered as an optional convenience.
9
+ * You may (OPTIONALLY) obtain free PLURNK_ACCOUNT and PLURNK_KEY variables at [plurnk.ai](https://plurnk.ai).
4
10
 
5
11
  ## Documentation
6
12
 
package/SPEC.md CHANGED
@@ -156,13 +156,13 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
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
 
159
- **One overlay.** Membership — `git ls-files ∪ addignore` with read-only (§membership) — is the session's: `session_constraints.session_id`, never a run. It is workspace *curation*, and the workspace *is* the session; two runs are two conversations about one curated workspace and see the same one. Divergent membership is a different session, never a per-run overlay. {§machine-processes-one-overlay}
159
+ **One overlay.** Membership — `git ls-files ∪ pickhide` with `view` read-only (§membership) — is the session's: `session_constraints.session_id`, never a run. It is workspace *curation*, and the workspace *is* the session; two runs are two conversations about one curated workspace and see the same one. Divergent membership is a different session, never a per-run overlay. {§machine-processes-one-overlay}
160
160
 
161
161
  **A run is its log — and nothing beside.** The run-private state is the log and only the log. *What I am looking at* (OPEN/FOLD) is `log_entries.expanded`, a bit on the run's own rows, toggled by ordinary `log:///` ops — not a second store, and never membership (§open-fold). *What I last saw* needs no shadow either: a run learns its world moved through log entries (§env-delta) — a sibling's write broadcast into its log, an out-of-band disk change detected against the entry's own content and broadcast the same way — never through a per-run snapshot the run cannot see. The log is the whole of a run's memory. {§machine-processes-run-is-its-log}
162
162
 
163
163
  **A run's log is private to packets, not to the session.** Isolation (§actor-boundary) governs what an *actor* sees — its own run, never a sibling's. It does not wall off the *wire*: any connection may read any run's log in its session by id — `log.read({ runId })`, ownership-verified, defaulting to the connection's own run. This is how a conversation client reads the **model** run, where the conversation lives: `loop.run` returns its `modelRunId`, and `session.runs` enumerates a session's runs for a connection that did not drive it live. The read is observation, never packet membership — no actor sees it. {§machine-processes-model-run-readable}
164
164
 
165
- **A run carries its actor.** Each run records its `origin` — `model` (the conversation), `client` (a connection's own run), or `plurnk` (the runtime's self-hosting run) — set once at creation and inherited by a fork. `session.runs` returns it, so a conversation client identifies the model run by its actor, not by parsing a renameable name. {§machine-processes-run-origin}
165
+ **A run carries its actor.** Each run records its `origin` — `model` (the conversation), `client` (a connection's own run), or `plurnk` (the runtime's self-hosting run) — set once at creation and inherited by a fork. `session.runs` returns it, so a conversation client identifies the model run by its actor, not by parsing the name — which is set at instantiation and immutable, never renamed (a run is permanent history, §machine-processes-run-is-its-log). {§machine-processes-run-origin}
166
166
 
167
167
  **Fork — copy the log, share the world.** A fork is a new run in the *same* session (`runs.parent_run_id`, §lifecycle-terms). It copies the **log** — the rows, their fold-state riding along — so the branch inherits everything the parent observed (§env-delta makes a run's timeline self-contained for exactly this) and diverges freely after. {§machine-processes-fork-copies-the-log} It shares the **world** — the one filesystem, the one overlay — live and uncopied, because the run never owned it. {§machine-processes-fork-shares-the-world}
168
168
 
@@ -856,9 +856,10 @@ registry.register("loop.run", {
856
856
  | `session.runs` | `id?: number` | `{ runs: Run[] }` | Lists runs in a session (defaults to attached session); most-recent first. |
857
857
  | `session.prompts` | `id?: number`, `limit?: number` | `{ prompts: string[] }` | A session's prior user prompts (the conversation run's loop seeds), newest-first, capped by `limit` (default 100); defaults to attached session. Lets a client seed up/down recall without log archaeology. |
858
858
  | `session.set_root` | `projectRoot: string \| null` | `{ projectRoot }` | Update the workspace pointer on the attached session. Null reverts to headless. |
859
- | `session.constrain` | `effect: "add" \| "ignore" \| "read-only"`, `glob: string` | `{ effect, glob }` | Add a workspace membership constraint (§membership overlay): `add` admits files git misses, `ignore` drops tracked matches, `read-only` admits for read but refuses edits. Immediate. |
860
- | `session.unconstrain` | `effect: "add" \| "ignore" \| "read-only"`, `glob: string` | `{ effect, glob }` | Remove a membership constraint — the inverse of `session.constrain`. Immediate. |
859
+ | `session.constrain` | `effect: "pick" \| "hide" \| "view" \| "repo"`, `glob: string` | `{ effect, glob }` | Add a workspace membership constraint (§membership overlay): `pick` admits a file git misses (the sole source when git is absent), `hide` drops a tracked match, `view` admits a member read-only (refused at the edit gate), `repo` declares a git repo folder anywhere so its members join the manifest. Immediate. |
860
+ | `session.unconstrain` | `effect: "pick" \| "hide" \| "view" \| "repo"`, `glob: string` | `{ effect, glob }` | Remove a membership constraint (the `drop` verb) — the inverse of `session.constrain`. Immediate. |
861
861
  | `session.constraints` | none | `{ constraints }` | List the attached session's membership constraints. |
862
+ | `session.members` | none | `{ members: [{ path, effect }], hidden }` | Resolve each project file's membership effect — `members` tagged `member`/`view` plus the `hide`-excluded `hidden` — so a client signs file visibility (member / read-only / ignored) without reimplementing the overlay glob-matching (§membership-resolved-effects). |
862
863
 
863
864
  **Re-binding.** `session.create` and `session.attach` may be called on a connection that already has a session attached — the connection switches in place, releasing the prior client loop (closed at 200). No reconnect needed to change session or run. {§methods-rebind}
864
865
 
@@ -1055,6 +1056,7 @@ Each entry: question, answer, rationale, migration path.
1055
1056
  - **`pick`** {§membership-overlay-pick} — admit an untracked file git misses: a targeted client-dictated `node:fs` glob scan over untracked matches (files only), 'constraint' origin, reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `pick` is the *sole* membership source.
1056
1057
  - **`hide`** {§membership-overlay-hide} — exclude a tracked file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-sensitive tracked file; `entries.membership_origin` keeps reconciliation off model-created members.
1057
1058
  - **`view`** {§membership-overlay-view} — keep a member readable but refuse `File.edit`, 403'd at the membership check before any diff. (Admitting an untracked file as `view` rides on `pick`'s scan.)
1059
+ - **Resolved effect is a read, not a re-derivation.** {§membership-resolved-effects} `session.members` surfaces each candidate's resolved effect — `(ls-files ∪ pick) − hide` tagged `member` / `view`, plus the `hide`-excluded `hidden` set — so a client signs file visibility (member / read-only / ignored) without reimplementing the overlay glob-matching. The daemon owns git + the globs; the per-file effect is its to resolve, the client's to render.
1058
1060
 
1059
1061
  **File ops act on the entry, not the disk; the two reconcile only at gates.** A `file:///` member is a row whose body channel holds the *materialized snapshot* of its disk content. READ returns that channel; EDIT diffs against it — neither reaches the filesystem directly. Entry and disk reconcile at exactly two gates: the **pre-turn materialize** (disk → entry, below) and the **accept-time write-back** (entry → disk, §proposal). Between the gates the entry is the truth the model curates against, and `synced_sig` — the member's last-synced disk stat (`mtime:size`) — is the version token both gates compare on.
1060
1062
 
@@ -1204,11 +1206,11 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
1204
1206
 
1205
1207
  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}
1206
1208
 
1207
- **First contributor: planning.** When `PLURNK_PLAN=1`, the grammar's `<<PLAN:...:PLAN` op is advertised here — in-band reasoning before acting. {§tools-plan-gated} The same hook is where each wired executor tag will later inject a line describing its tag and functionality (the boot `ExecutorRegistry` probes availability per tag), retiring the model's blind `<<EXEC[sh]…`.
1209
+ **Contributors: the wired executor tags.** Each available executor tag injects a line describing its tag and functionality (the boot `ExecutorRegistry` probes availability per tag), retiring the model's blind `<<EXEC[sh]…`. The plan directive does **not** render here: it is a hard requirement gated by `PLURNK_PLAN`, joined to and dropped from the rules list with the flag rather than softly advertised as an optional tool (§requirements-plan-gated).
1208
1210
 
1209
1211
  ### §requirements user.system_requirements — static per-turn rules
1210
1212
 
1211
- 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`").
1213
+ 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, and when `PLURNK_PLAN=1` the plan directive — *YOU MUST begin every response with `<<PLAN:...:PLAN`* — joins the rules: a hard requirement dynamically added to and removed from the list with the flag, never the soft optional-tools sheet. {§requirements-plan-gated}
1212
1214
 
1213
1215
  **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.
1214
1216
 
@@ -1 +1 @@
1
- {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAwF,MAAM,wBAAwB,CAAC;AAMpJ,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAiB,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAU9C,OAAO,KAAK,EAAkB,UAAU,EAAuB,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACpG,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA8DlI,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAG9E,OAAO,KAAK,EAAE,QAAQ,EAAsD,MAAM,0BAA0B,CAAC;AA2C7G,KAAK,eAAe,GAAG;IACnB,SAAS,EAAE,eAAe,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAOjF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9D,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAK3B,IAAI,CAAC,EAAE,MAAM,CAAC;IAKd,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAYD,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IAIjB,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAuGD,MAAM,CAAC,OAAO,OAAO,MAAM;;IACvB,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAUhF,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM;IAQnE,MAAM,CAAC,WAAW,CACd,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACvB;QAAE,QAAQ,EAAE,KAAK,CAAA;KAAE,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;gBAwE/D,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,EAAE,EAAE;QAC5H,EAAE,EAAE,EAAE,CAAC;QACP,OAAO,EAAE,cAAc,CAAC;QACxB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;QACtC,aAAa,CAAC,EAAE,aAAa,CAAC;QAC9B,SAAS,CAAC,EAAE,eAAe,CAAC;QAC5B,SAAS,CAAC,EAAE,eAAe,CAAC;QAC5B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;QAC5C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;KACvC;IAwBD,YAAY,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IA6BzC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAgDxG,OAAO,CAAC,EACV,QAAQ,EAAE,QAAQ,EAAE,YAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAC/D,QAAa,EAAE,UAA6B,EAC5C,SAAoE,EACpE,cAAqF,EACrF,MAAgB,EAAE,MAAM,EAAE,UAAU,GACvC,EAAE;QACC,QAAQ,EAAE,QAAQ,CAAC;QACnB,QAAQ,EAAE,WAAW,EAAE,CAAC;QAIxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;KAC7C,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB,GAAG,iBAAiB,GAAG,UAAU,GAAG,IAAI,CAAA;KAAE,CAAC;IA+HzJ,OAAO,CAAC,EACV,QAAQ,EAAE,QAAQ,EAAE,YAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAgB,EAAE,MAAM,EAAE,UAAU,EACrG,UAAc,EAAE,QAAa,GAChC,EAAE;QACC,QAAQ,EAAE,QAAQ,CAAC;QACnB,QAAQ,EAAE,WAAW,EAAE,CAAC;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QACjD,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;QAK1C,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IAygBxI,UAAU,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAgPhD,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IA6LjE,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAYzE,kBAAkB,IAAI,MAAM,EAAE;IAQxB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBpD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAChD;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAC7C;IAgCD,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI;CAigB3E"}
1
+ {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
@@ -859,7 +859,12 @@ class Engine {
859
859
  // exactly how it went missing (callers read the sysprompt but never the
860
860
  // requirements). Read Paths.defaultRequirements (PLURNK_REQUIREMENTS env →
861
861
  // requirements.md) fresh each build so edits take effect; a non-empty param wins.
862
- const requirementsText = requirements.length > 0 ? requirements : await readFile(Paths.defaultRequirements, "utf8");
862
+ const baseRequirements = requirements.length > 0 ? requirements : await readFile(Paths.defaultRequirements, "utf8");
863
+ // The op syntax leads the requirements; when PLURNK_PLAN=1 the plan directive joins the
864
+ // HARD requirements list (dynamically added/removed with the flag) rather than softly
865
+ // appearing in the optional # Plurnk System Tools sheet. §requirements-plan-gated
866
+ const planDirective = process.env.PLURNK_PLAN === "1" ? "YOU MUST begin every response with <<PLAN:...:PLAN\n" : "";
867
+ const requirementsText = `Syntax: <<OPsuffix[signal]?(target)?<Line/Result>?:body?:OPsuffix\n\n${planDirective}${baseRequirements}`;
863
868
  const log = await this.#buildLog(runId);
864
869
  const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
865
870
  // Per-section render-cost subtotals via provider's tokenizer.
@@ -926,18 +931,15 @@ class Engine {
926
931
  }
927
932
  return lines.join("\n");
928
933
  }
929
- // The # Plurnk System Tools capability sheet (SPEC §tools). A hook: each
930
- // enabled capability contributes one line, rendered above Requirements so
931
- // the model sees what it can do before the rules. PLAN is the first
932
- // contributor (gated by PLURNK_PLAN); each available executor tag then
933
- // contributes its self-documenting example (plurnk-execs#7), retiring the
934
- // blind EXEC.
935
- // The capability sheet — the live tool surface (PLAN + wired executor tags). §tools-capability-sheet
934
+ // The # Plurnk System Tools capability sheet (SPEC §tools). A hook: each enabled
935
+ // capability contributes one line, rendered above Requirements so the model sees what
936
+ // it can do before the rules. Each available executor tag contributes its self-documenting
937
+ // example (plurnk-execs#7), retiring the blind EXEC. The plan directive is NOT a tool — it
938
+ // is a hard requirement gated by PLURNK_PLAN (§requirements-plan-gated), so it joins the
939
+ // rules list, not this optional sheet.
940
+ // The capability sheet — the live tool surface (wired executor tags). §tools-capability-sheet
936
941
  #collectTools() {
937
942
  const tools = [];
938
- if (process.env.PLURNK_PLAN === "1") { // <<PLAN advertised only when PLAN is enabled — §tools-plan-gated
939
- tools.push("* Begin every response with <<PLAN:...:PLAN");
940
- }
941
943
  // Each available runtime tag contributes its self-documenting example —
942
944
  // the example carries syntax + purpose, so there's no prose line. Tags
943
945
  // with no example (sh/node, covered by the core prompt) contribute