@plurnk/plurnk-contracts 1.16.5 → 1.18.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 (92) hide show
  1. package/README.md +13 -62
  2. package/SPEC.md +633 -529
  3. package/dist/conformance/agui-v1.json +3 -50
  4. package/dist/schema/CapabilityDescriptor.json +6 -1
  5. package/dist/schema/CapabilityProjection.json +2 -4
  6. package/dist/schema/CapabilitySelector.json +6 -1
  7. package/dist/schema/ClientStatement.json +5 -52
  8. package/dist/schema/FunctionalityDefinitionState.json +10 -2
  9. package/dist/schema/LineMarker.json +1 -1
  10. package/dist/schema/LoopPolicy.json +2 -3
  11. package/dist/schema/MatcherBody.json +6 -6
  12. package/dist/schema/McpServerDefinition.json +17 -0
  13. package/dist/schema/ModelCatalogPage.json +10 -1
  14. package/dist/schema/ModelRoute.json +9 -0
  15. package/dist/schema/Notice.json +1 -1
  16. package/dist/schema/ParsedPath.json +3 -2
  17. package/dist/schema/Plan.json +10 -6
  18. package/dist/schema/PlurnkStatement.json +95 -143
  19. package/dist/schema/ProposalProjection.json +6 -1
  20. package/dist/schema/ResourceSelection.json +51 -27
  21. package/dist/schema/SkillDefinition.json +3 -3
  22. package/dist/src/AcpPlanValue.d.ts +0 -1
  23. package/dist/src/AcpPlanValue.d.ts.map +1 -1
  24. package/dist/src/AcpPlanValue.js +14 -13
  25. package/dist/src/AcpPlanValue.js.map +1 -1
  26. package/dist/src/ApplicationPort.d.ts +17 -14
  27. package/dist/src/ApplicationPort.d.ts.map +1 -1
  28. package/dist/src/JsonDocument.d.ts +2 -0
  29. package/dist/src/JsonDocument.d.ts.map +1 -0
  30. package/dist/src/JsonDocument.js +14 -0
  31. package/dist/src/JsonDocument.js.map +1 -0
  32. package/dist/src/LoopLifecycle.d.ts +3 -0
  33. package/dist/src/LoopLifecycle.d.ts.map +1 -0
  34. package/dist/src/LoopLifecycle.js +14 -0
  35. package/dist/src/LoopLifecycle.js.map +1 -0
  36. package/dist/src/PlanValue.d.ts +1 -1
  37. package/dist/src/PlanValue.d.ts.map +1 -1
  38. package/dist/src/PlanValue.js +10 -6
  39. package/dist/src/PlanValue.js.map +1 -1
  40. package/dist/src/PlurnkParseError.d.ts +3 -1
  41. package/dist/src/PlurnkParseError.d.ts.map +1 -1
  42. package/dist/src/PlurnkParseError.js +4 -1
  43. package/dist/src/PlurnkParseError.js.map +1 -1
  44. package/dist/src/TurnDisposition.d.ts +11 -0
  45. package/dist/src/TurnDisposition.d.ts.map +1 -0
  46. package/dist/src/TurnDisposition.js +32 -0
  47. package/dist/src/TurnDisposition.js.map +1 -0
  48. package/dist/src/Validator.js +1 -1
  49. package/dist/src/Validator.js.map +1 -1
  50. package/dist/src/index.d.ts +5 -4
  51. package/dist/src/index.d.ts.map +1 -1
  52. package/dist/src/index.js +5 -5
  53. package/dist/src/index.js.map +1 -1
  54. package/dist/src/types.d.ts +13 -8
  55. package/dist/src/types.d.ts.map +1 -1
  56. package/dist/src/types.generated.d.ts +172 -86
  57. package/dist/src/types.generated.d.ts.map +1 -1
  58. package/dist/src/types.js +9 -4
  59. package/dist/src/types.js.map +1 -1
  60. package/package.json +5 -23
  61. package/plurnk.md +106 -119
  62. package/bin/plurnk-contracts.js +0 -43
  63. package/dist/plurnk.gemma.gbnf +0 -141
  64. package/dist/plurnk.qwen.gbnf +0 -130
  65. package/dist/src/AstBuilder.d.ts +0 -20
  66. package/dist/src/AstBuilder.d.ts.map +0 -1
  67. package/dist/src/AstBuilder.js +0 -723
  68. package/dist/src/AstBuilder.js.map +0 -1
  69. package/dist/src/PlurnkErrorStrategy.d.ts +0 -11
  70. package/dist/src/PlurnkErrorStrategy.d.ts.map +0 -1
  71. package/dist/src/PlurnkErrorStrategy.js +0 -358
  72. package/dist/src/PlurnkErrorStrategy.js.map +0 -1
  73. package/dist/src/PlurnkParser.d.ts +0 -11
  74. package/dist/src/PlurnkParser.d.ts.map +0 -1
  75. package/dist/src/PlurnkParser.js +0 -335
  76. package/dist/src/PlurnkParser.js.map +0 -1
  77. package/dist/src/RecordingListener.d.ts +0 -9
  78. package/dist/src/RecordingListener.d.ts.map +0 -1
  79. package/dist/src/RecordingListener.js +0 -19
  80. package/dist/src/RecordingListener.js.map +0 -1
  81. package/dist/src/generated/plurnkLexer.d.ts +0 -174
  82. package/dist/src/generated/plurnkLexer.d.ts.map +0 -1
  83. package/dist/src/generated/plurnkLexer.js +0 -1215
  84. package/dist/src/generated/plurnkLexer.js.map +0 -1
  85. package/dist/src/generated/plurnkParser.d.ts +0 -477
  86. package/dist/src/generated/plurnkParser.d.ts.map +0 -1
  87. package/dist/src/generated/plurnkParser.js +0 -3298
  88. package/dist/src/generated/plurnkParser.js.map +0 -1
  89. package/dist/src/generated/plurnkParserVisitor.d.ts +0 -284
  90. package/dist/src/generated/plurnkParserVisitor.d.ts.map +0 -1
  91. package/dist/src/generated/plurnkParserVisitor.js +0 -245
  92. package/dist/src/generated/plurnkParserVisitor.js.map +0 -1
package/SPEC.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## 1. Overview
4
4
 
5
5
  §contract-authority This package is the single authority for PLURNK's language, schemas, generated
6
- types, parser, model rail, and runtime-neutral wire envelopes. Its package root
6
+ types, parser, and runtime-neutral wire envelopes. Its package root
7
7
  is the single code API for those contracts.
8
8
 
9
9
  | Surface | Canonical export or artifact |
@@ -23,12 +23,12 @@ is the single code API for those contracts.
23
23
  | AG-UI discovery, client accounting, and shared conformance specimens | `AguiDiscovery`, `AguiClientConformance`, `AguiConformanceKit` |
24
24
  | JSON Schemas | `@plurnk/plurnk-contracts/schema/*.json` |
25
25
  | Generated JSON result rendering | `renderJsonResult` |
26
- | Local-model rails | `@plurnk/plurnk-contracts/plurnk.{gemma,qwen}.gbnf` |
27
26
  | Model language reference | `plurnk.md` in the package |
28
27
 
29
28
  §contract-representations JSON Schema is authoritative for shared data shapes. TypeScript types are
30
29
  generated from the schemas; ANTLR is authoritative for accepted model-language
31
- syntax; GBNF remains the bounded generation aid described in §1.2.
30
+ syntax. No generation grammar is generated or shipped; an operator's own GBNF
31
+ is carried verbatim to a llama-server route by the providers package.
32
32
 
33
33
  §agui-discovery-contract `AguiDiscovery` is the complete installed AG-UI+
34
34
  surface at one instant. `schemaVersion` identifies its discovery shape;
@@ -46,6 +46,10 @@ not satisfy the advertised `inputSchema`, rejects an owner's successful output
46
46
  when it does not satisfy `outputSchema`, and validates a known notification
47
47
  before projecting it to AG-UI. Schemas are discovery values owned by their
48
48
  registrants; validation must not annotate or otherwise mutate them.
49
+ Input Problems retain the structured validation `issues` and name the failing
50
+ instance locations and constraints in `detail`. Parent aggregate errors are
51
+ omitted from that prose when their specific child errors are available; input
52
+ objects are never echoed wholesale or interpreted as intent.
49
53
 
50
54
  §agui-client-conformance `AguiClientConformance` is a language-neutral JSON
51
55
  document accounting for every action and notification in one
@@ -69,30 +73,37 @@ a third protocol implementation: each client feeds the same chunks and events
69
73
  through its production parser and projection seam, then verifies the declared
70
74
  outcome. Specimen names are unique within their transport or lifecycle family.
71
75
 
72
- §json-result-rendering `renderJsonResult` is the one presentation serializer
73
- for generated JSON operation results. A top-level array remains one valid,
76
+ §json-result-rendering `renderJsonResult` renders compact aggregate operation
77
+ rows. A top-level array remains one valid,
74
78
  compact JSON value but places each item on its own physical line by adding only
75
79
  item-boundary newlines; an empty or single-item array and every non-array value
76
80
  remain one line. It never rewrites arbitrary stored JSON, whose original lines
77
81
  remain source coordinates.
78
82
 
83
+ §json-document-presentation Generated JSON documents use two-space indentation.
84
+ Normalized remote JSON text uses `formatJsonDocument`: whitespace-only formatting
85
+ of a complete, strict JSON document, preserving key order, duplicate keys, number
86
+ lexemes, and string escapes. Invalid or incomplete input is declined, not repaired.
87
+ Apply formatting at the representation owner before storage, indexing, scoping,
88
+ and previews; never reformat literal resources, JSONL framing, or wire/evidence
89
+ serialization. Compact aggregate rows ({§json-result-rendering}) and packet
90
+ metadata retain their deliberate layouts.
91
+
79
92
  ## §contract-layers 1.1 Contract layers and admission boundary
80
93
 
81
94
  PLURNK uses one contract with deliberately different projections. A tolerant
82
95
  ingester accepting a spelling does not make that spelling canonical model
83
- teaching, and a generation rail admitting a sentence does not make its runtime
84
- semantics valid.
96
+ teaching, and an operator's sampling grammar admitting a sentence does not
97
+ make its runtime semantics valid.
85
98
 
86
99
  ```mermaid
87
100
  flowchart LR
88
101
  canon["Canonical model teaching<br/>plurnk.md"]
89
- rail["Optional raw generation rail<br/>Gemma or Qwen template profile"]
90
102
  free["Other admitted input"]
91
103
  syntax["ANTLR lexer + parser<br/>syntax and document tier"]
92
104
  ast["AstBuilder<br/>typed, serializable AST"]
93
105
  runtime["Runtime owners<br/>stateful semantics and effects"]
94
- canon --> rail
95
- rail --> syntax
106
+ canon --> syntax
96
107
  canon --> free
97
108
  free --> syntax
98
109
  syntax --> ast
@@ -103,20 +114,19 @@ flowchart LR
103
114
  |--------------------------|-------------------------------------|---------------------------------------------------------------------------------|
104
115
  | Stable current law | `SPEC.md` | Owns invariants and boundaries; forge issues retain history |
105
116
  | Canonical model teaching | `plurnk.md` | Teaches the lean spelling and operational model the model should emit |
106
- | Constrained generation | generated `plurnk.*.gbnf` | Increases likely ANTLR compliance without reproducing all parser/runtime checks |
107
- | Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, heading lanes, slot shape, and section boundaries |
117
+ | Accepted syntax | `plurnkLexer.g4`, `plurnkParser.g4` | Recognizes document tiers, operation fences, slot shape, and section boundaries |
108
118
  | Typed admission | `AstBuilder` | Produces JSON-serializable unions and validates deterministic body/path syntax |
109
119
  | Shared wire data | `schema/*.json` | Defines runtime-neutral data shapes projected into generated TypeScript |
110
120
  | Stateful behavior | consuming runtime | Resolves addresses, permissions, selection arithmetic, effects, and lifecycle |
111
121
 
112
- ANTLR owns statement structure, delimiter matching, slot multiplicity, accepted
122
+ ANTLR owns statement structure, fence matching, slot multiplicity, accepted
113
123
  slot permutations, scope-number syntax, and interstatement text recognition.
114
124
  AstBuilder owns URL decomposition and deterministic matcher validation through
115
125
  WHATWG `URL`, ECMAScript `RegExp`, XPath 1.0, and RFC 9535 JSONPath parsers.
116
126
 
117
127
  The runtime owner decides facts that require state or operation-specific
118
128
  meaning, including registered scheme resolution, target existence, tag
119
- selection, text-region bounds, result ordering, semantic similarity, mutation
129
+ selection, text-region bounds, result ordering, full-text ranking, mutation
120
130
  effects, executor behavior, and numeric operation-code semantics.
121
131
 
122
132
  ### §contract-proposal-projection Loop policy and stopped-world projection
@@ -126,9 +136,9 @@ The schemas own the runtime-neutral shapes; core owns their stateful values.
126
136
  | Contract | Shape invariant | Runtime responsibility |
127
137
  | ------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
128
138
  | `CapabilityDescriptor` | One routed operation demand with its operation, access class, resource/runtime/tool coordinates, and declared traits | Derive every demand before dispatch |
129
- | `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service, workspace, Worker, and loop layers |
130
- | `CapabilityProjection` | Exact service, workspace, immutable Worker bound, mutable Worker, and normalized effective policies | Expose the resolver's Worker-level cascade to clients without claiming one layer is effective authority |
131
- | `LoopPolicy` | Complete capability attenuation plus one `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
139
+ | `CapabilityPolicy` | Exact `only`/`deny` selectors; omitted `only` is unrestricted and present empty `only` denies all | Intersect service and workspace layers |
140
+ | `CapabilityProjection` | Exact service, workspace, and normalized effective policies | Expose the resolver's workspace cascade without claiming one layer is effective authority |
141
+ | `LoopPolicy` | One `review`, `accept`, or `reject` proposal disposition | Snapshot once when the loop is created |
132
142
  | `ProposalDisposition` | Client authority, or the loop's exact automatic accept/reject | Compute precedence from effective loop policy, proposal kind, and stale-target truth |
133
143
  | `ProposalProjection` | Identity, `{ scheme, authority, pathname }` review target, body/attrs, effective policy, stale signal, disposition | Derive one validated projection for live delivery and durable reconnect discovery |
134
144
  | `ProviderUsage` | Conventional input/output totals with cache and reasoning details | Preserve observed quantities without replacing absence with zero |
@@ -144,25 +154,24 @@ least one selector must match. An empty policy admits everything and an empty
144
154
 
145
155
  §capability-policy-cascade Capability layers are purely subtractive and
146
156
  order-independent: a descriptor is admitted only when every layer admits it.
147
- No Worker or loop can restore service, workspace, or parent authority. A
157
+ Workspace policy cannot restore authority denied by the service. A
148
158
  composed operation is admitted only when every routed demand survives. These
149
159
  descriptors govern routed external authority, not every grammar statement:
150
- log/program control such as PLAN, log KILL, and label or targetless SEND
151
- creates no capability demand. A known interactive runtime is represented by
160
+ log/program control such as log KILL, the native dispositions, and
161
+ targetless SEND creates no capability demand. A known interactive runtime is represented by
152
162
  access class `interact`; scheme and runtime manifests contribute traits rather
153
163
  than hidden policy behavior.
154
164
 
155
165
  §capability-policy-projection A `CapabilityProjection` reports every durable
156
- Worker-level layer and their normalized intersection. The `worker` field is the
157
- only client-mutable layer; `effective` is the authority a new unattenuated loop
158
- would receive. A client never derives effective authority from the mutable
159
- layer alone. Per-loop attenuation remains an immutable input to that loop and
160
- is therefore absent from this durable Worker projection.
166
+ workspace layer and their normalized intersection: `service`, `workspace`, and
167
+ `effective`. Only `workspace` is client-mutable. Workers and loops have no
168
+ capability policy or inherited bound; every actor uses the same live workspace
169
+ policy. A client never derives effective authority from the mutable layer alone.
161
170
 
162
171
  §loop-policy `DEFAULT_CAPABILITY_POLICY` and `DEFAULT_LOOP_POLICY` are the
163
172
  contracts-owned complete defaults. A loop policy is immutable after creation;
164
- its `capabilities` field only narrows broader authority and its `proposals`
165
- field chooses one unambiguous downstream settlement posture. Capability
173
+ its `proposals` field chooses one downstream settlement posture, independently
174
+ of workspace capability policy. Capability
166
175
  admission precedes effect classification and proposal settlement.
167
176
 
168
177
  §reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
@@ -178,6 +187,10 @@ physical limits, capabilities, and local `ModelReadiness`. A readiness cause
178
187
  contains alternative environment-variable sets—every name within a set is
179
188
  required and any set may satisfy the cause. It carries names only, never values,
180
189
  and asserts neither credential validity nor endpoint reachability.
190
+ `capabilities.reasoningPolicies` lists the route's admitted members of
191
+ {§reasoning-policy-wire}, including supported activation policies; clients do
192
+ not infer fixed efforts from the `reasoning` capability bit. It is not a
193
+ worker's model/spawn intersection or an alias-specific tuning projection.
181
194
 
182
195
  ### §client-interaction-wire Client-owned interaction wire
183
196
 
@@ -229,195 +242,240 @@ USD-expressible request and is `null` only when none is expressible. The empty
229
242
  request set projects explicit zero usage and cost. Consumers do not recompute
230
243
  provider rates or convert currencies while reading the projection.
231
244
 
232
- The parser returns ordered statement, error, and text items. It recovers at a
245
+ The parser ignores outside text and returns ordered statement and error items
246
+ under {§whitespace-contract}. It recovers at a
233
247
  trustworthy statement boundary when possible and sets `unparsedTail` when a
234
- boundary-destroying failure makes later input undefined. SEND operation codes
248
+ boundary-destroying failure makes later input undefined. Operation status codes
235
249
  and parse diagnostics are separate contracts.
236
250
 
237
- ## 1.2 GBNF Generation Rail
238
-
239
- §gbnf-rail-purpose ANTLR and AstBuilder define accepted PLURNK input. The generated
240
- `dist/plurnk.{gemma,qwen}.gbnf` are optional local llama.cpp sampling rails kept lean
241
- to make useful, ANTLR-compliant turns more likely without reproducing every
242
- parser or semantic validator. Parse compatibility is a design goal balanced
243
- against rail size and sampling efficiency, not a language-subset guarantee. A
244
- rail-legal operation can therefore produce a parser or AstBuilder error; consumers
245
- apply their ordinary admission and bounded-operation recovery contract.
246
- The complete package build emits both rails; they are not source-controlled. Source and
247
- differential tests serialize the owning generator directly, while installation
248
- coverage verifies the packed export.
249
-
250
- The rails share one turn shape but begin at their respective sampled-token
251
- boundaries:
252
-
253
- ```ebnf
254
- root-gemma ::= channel sep framed-turn
255
- root-qwen ::= think-body think-close sep framed-turn
256
- root-qwen-response ::= think-open root-qwen
257
- framed-turn ::= turn | fence-open turn fence-close
258
- turn ::= plan sep tail-0
259
- ```
260
-
261
- §gbnf-turn-shape Neither rail admits an empty thought: the `gemma` channel body and the `qwen` think body each begin with at least one character, so a constrained call reasons before it acts. The `gemma` transport root samples one complete
262
- `<|channel>thought\n … <channel|>` enclosure. A Qwen-style chat template has
263
- already supplied `<think>\n` when the `qwen` transport root begins, so that root
264
- samples the body and required `</think>` closer. Each generated artifact declares
265
- an `@plurnk-response-root`; for `qwen`, that root composes the template opener
266
- back onto the sampled text so the complete pre-projection response can be graded.
267
- Either body may be empty and cannot contain its profile's opener or closer.
268
- `sep` is zero through seven whitespace characters. The projected PLURNK content
269
- is either bare or enclosed once in a paired `plurnk` Markdown fence; the turn
270
- begins with `## PLAN0`, and every following operation is a same-lane `## OP0`
271
- section.
272
- `tail-0` admits zero through fourteen internal operations followed by exactly
273
- one terminal SEND under the existing terminal-eligibility rules.
274
-
275
- ```mermaid
276
- flowchart LR
277
- sampled["Constrained sampled text<br/>profile reasoning bytes · sep · optional fence · PLAN0 turn"]
278
- raw["Pre-projection response<br/>one complete reasoning envelope · PLURNK turn"]
279
- split["llama.cpp<br/>reasoning_format: auto"]
280
- reasoning["reasoning_content<br/>envelope body"]
281
- content["content<br/>bare or fenced PLAN through terminal SEND"]
282
- parser["ANTLR + AstBuilder<br/>admission and diagnostics"]
283
- sampled --> raw
284
- raw --> split
285
- split --> reasoning
286
- split --> content
287
- content --> parser
288
- ```
289
-
290
- §gbnf-reasoning-boundary GBNF applies from sampled token zero before response
291
- projection. The declared response root composes any template-provided prefix for
292
- independent validation of the pre-projection evidence. The two projected fields
293
- are not separate GBNF languages, and `content` alone is not revalidated as though
294
- it still contained the required reasoning envelope. Provider and core own the
295
- projection evidence and rail-verdict boundary; this package owns the sampled and
296
- response roots plus the parser/AstBuilder result.
297
-
298
- §rail-heading-boundaries On the GBNF rail, PLAN and every operation use lane `0`.
299
- Every reserved PLAN or operation heading stem is structural, regardless of the
300
- delimiter a model attempts next, so a non-`0` pseudo-heading cannot be swallowed as
301
- literal body text. Rail bodies therefore cannot quote reserved headings from any
302
- lane. This makes both the canonical delimiter and section boundary structurally
303
- available during constrained generation; ANTLR remains the wider language and
304
- accepts intentional alternate-lane literals during ingestion.
305
-
306
- §gbnf-kill-shaping The rail shapes KILL as one required target, an optional
307
- text-coordinate scope (numeric or anchored), and an optional one-line matcher body,
308
- without proving that the selection resolves; ANTLR and AstBuilder own the statement's
309
- shape, and runtime owns target resolution ({§kill-scope}).
310
-
311
251
  ## §canonical-statement 2. Canonical statement form
312
252
 
313
- ```text
314
- ## PLANdelimiter
253
+ `````text
254
+ ```OP (path)? <scope>? [metadata]? <!-- aside -->?
315
255
  body
256
+ ```
316
257
 
317
- ## OPdelimiter (path)? {metadata}* <scope>? <!-- annotation -->?
318
- body?
258
+ ```OP (path)? <scope>?
319
259
  ```
320
260
 
321
- §section-boundary A statement is one Markdown section. PLAN alone uses a level-one
322
- heading; every other operation uses a level-two heading. Its body is the
323
- character-perfect section content before the next structural heading or EOF.
324
- Canonical adjacent sections place the next structural heading on the immediately
325
- following line. The tolerant ingester also admits one empty separator line; that
326
- separator is syntax rather than body content, while any additional preceding
327
- blank lines remain body content.
328
-
329
- §empty-section An empty section has no body lines between its heading and the next
330
- structural heading, tolerated separator, or EOF. Optional operation bodies normalize
331
- to null; PLAN admission normalizes its required semantic body to `[]`
332
- under {§plan-value}.
333
-
334
- | Element | Canonical contract |
335
- |--------------|---------------------------------------------------------------------------|
336
- | `## PLAN` | Required level-one turn anchor |
337
- | `## OP` | Level-two protocol operation |
338
- | `delimiter` | Heading lane, joined directly to PLAN or OP |
339
- | `(path)` | Optional target slot, preceded by one space |
340
- | `{metadata}` | Optional repeatable scheme-metadata modifier after a target |
341
- | `<scope>` | Optional numeric scope, preceded by one space |
342
- | `<!-- … -->` | Optional trailing operation annotation, preceded by one space |
343
- | line ending | Ends the single-line heading |
344
- | `body` | Zero or more characters of operation-specific, character-perfect content |
345
- | blank line | Canonical section separator; excluded from the preceding body |
346
-
347
- The following constraints are structural:
348
-
349
- - §lane-match PLAN establishes one lane for the turn. A heading is structural
350
- only when its delimiter character-matches that lane; a different delimiter remains
351
- ordinary body text.
352
- - PLAN is the only H1 operation and every non-PLAN operation is H2.
353
- - A header occupies one physical line.
354
- - Each admitted signal, target, and scope slot appears at most once. Metadata
355
- blocks may repeat only immediately after the target.
356
- - §plan-slotless PLAN accepts no signal, target, metadata, or scope modifier;
357
- observed modifiers are a bounded hard error naming only the rejected slots.
358
- - An annotation follows every present modifier and appears at most once.
359
- - BARE, WORK, FORK, and KILL do not admit a scope slot.
360
- - An ingested delimiter is `[A-Za-z0-9_]*`; canonical teaching and the GBNF use `0`.
361
-
362
- §slot-order Canonical producers and the GBNF rail emit signal, then target, then
363
- metadata, then scope, then annotation, with one ASCII space before every present modifier. Slot delimiters make
364
- their boundaries unambiguous, so the tolerant ANTLR ingester accepts zero or
365
- more horizontal whitespace characters before each slot and any permutation of
366
- the signal, target-with-metadata, and scope admitted by that operation, at most
367
- once each. Metadata remains attached immediately after its target. Accepted
368
- spacing and permutation are not second canonical spellings.
369
-
370
- §heading-inline-body Text that follows the last slot on a heading line — after
371
- horizontal whitespace, beginning with a character that cannot open a slot (not `[`,
372
- `(`, or `<`, nor `{` after a target) — is the first body line: `### EXEC0 [crm] (crm_query) SELECT Id FROM Case` and
373
- `### FIND0 (src/**) /createCoder/i` parse as their canonical two-line forms. Nothing is
374
- lost and the stored statement is canonical; the spelling is tolerated and announced:
375
- one warning-severity advisory follows the statement, naming the heading and the rule
376
- (body content goes immediately beneath the OP heading line), so the model learns
377
- the form from the packet and never from silent acceptance.
378
-
379
- §operation-annotation A heading may end with one single-line Markdown HTML
380
- comment. AstBuilder strips the delimiters and surrounding horizontal whitespace
381
- into the statement's fixed `annotation: string | null` field. The annotation is
382
- durable, model- and client-facing descriptive text but semantically inert: it
383
- does not alter operation identity, signal, target, scope, dispatch, effect,
384
- authorization, status, or body. An empty comment normalizes to the empty string.
385
- Text containing a newline or lacking the closing `-->` is not an annotation;
386
- `<!--` elsewhere remains ordinary body text.
387
-
388
- §scheme-metadata-modifier A target may be followed by zero or more
389
- single-line `{metadata}` blocks. AstBuilder preserves each block's exact inner
390
- text and order as the statement's `metadata: string[] | null`; nested braces
391
- remain balanced content. The blocks are not part of the target: braces inside
392
- `(path)` remain ordinary path and glob syntax, including `{PLAN,READ}`. The
393
- language assigns metadata no meaning. A runtime admits it only for a scheme
394
- that declares the capability, and that scheme exclusively owns interpretation,
395
- validation, and authorization. An unfinished block or a newline before its
396
- closing brace is a structural failure.
397
-
398
- The ingester also accepts several bounded noncanonical forms so it can explain
399
- or safely execute understandable input:
400
-
401
- | Tolerated input | Canonical or runtime disposition |
402
- |---------------------------------------------------|---------------------------------------------------------------------|
403
- | Reordered admitted slots | Producers retain signal → target → metadata → scope order |
404
- | Missing target on a generally targeted operation | AST carries `null`; the runtime rejects when the target is required |
405
- | A non-`0` PLAN lane | Model canon uses lane `0` |
406
- | KILL annotation body | AST preserves it; model teaching leaves the KILL section empty |
407
- | Dash-separated or comma-space scope numbers | Producers use adjacent comma-separated numbers |
408
- | Empty content where semantics require a body | The empty section normalizes null; the operation owner rejects it |
261
+ ```executor (program-or-tool)?
262
+ input
263
+ ```
264
+ `````
265
+
266
+ §section-boundary Every statement is one backtick block. Its header occupies one
267
+ physical line: a fence of at least three backticks, an optional numeric delimiter
268
+ ({§numeric-delimiter}), then the name and its slots. A closer is shown by
269
+ convention and never demanded ({§fence-closer}, {§closer-fallback}). There are no
270
+ operation suffixes or heading levels. Nothing in the language is counted by the
271
+ author: every boundary is an anchored line the parser recognizes by its first
272
+ characters (operator, 2026-09-12: counted pairs failed 52 of 363 turns on
273
+ GLM-5.3-flash; anchored tokens failed none).
274
+
275
+ §fence-closer A block opened with N backticks and delimiter D (its digits, possibly
276
+ none) closes at the first line at column zero made of at least N backticks,
277
+ exactly D, and nothing else but horizontal whitespace. Count follows CommonMark: a
278
+ shorter fence inside the body is body; an equal or longer bare fence closes a bare
279
+ block. The delimiter compares exactly: a bare fence never closes a delimited block,
280
+ and a delimited fence never closes a bare one. The compact one-line form closes on
281
+ its heading line after the modifiers under the same rule.
282
+
283
+ §numeric-delimiter Digits between the opening backticks and the name (an opener
284
+ carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it. This is how a
285
+ block nests fences of its own width: with a delimiter, a body may carry bare fences
286
+ and headings of the same count. The delimiter is syntax, never AST or persistence
287
+ state; `PlurnkParser.frame` chooses one when the body it wraps holds a heading line
288
+ of four or more backticks ({§statement-rendering}).
289
+
290
+ §fence-heading-in-body A fence line of four or more backticks, optional digits, and
291
+ a name that is a native operation or a known executor is a heading wherever it
292
+ stands. Inside an open block it ends that block without closing it
293
+ ({§closer-fallback}) and opens the next statement. Fence lines of fewer than four
294
+ backticks are headings only outside any block. Known executors are `sh` plus what
295
+ the host names in `ParseOptions.executors`. Consequences: a closer glued to the next
296
+ opener (eight backticks then `READ`) can never swallow the rest of a turn, and a quoted
297
+ heading of four or more backticks inside a body needs the numeric delimiter to stay body.
298
+
299
+ §closer-fallback A block that ends at a heading or at the end of the input has no
300
+ closer of its own. Its body is cut back to its last bare fence line (any count,
301
+ optional digits), which is the closer the author meant, and one terminating line
302
+ ending goes with it; when no bare fence line exists the body is the whole span less
303
+ one terminating line ending. This carries no diagnostic: a missing closer is never
304
+ an admission failure, and {§unparsed-tail-boundary} is not involved.
305
+
306
+ §fence-boundary Inside a body, fences are read by count and delimiter, never by
307
+ name, except for the heading rule above:
308
+
309
+ | Fence encountered inside a body | Meaning |
310
+ |---|---|
311
+ | Fewer backticks than the block's own | Body |
312
+ | At least the block's backticks, bare, block undelimited | The block's closer |
313
+ | At least the block's backticks carrying the block's delimiter | The block's closer |
314
+ | At least the block's backticks with any other delimiter | Body |
315
+ | Four or more backticks naming a native operation or known executor | A heading: ends the block, opens the next statement |
316
+
317
+ §indented-fences Leading horizontal whitespace before an opener or a closer is
318
+ not part of the fence: an indented fence line is a fence line, on openers,
319
+ closers, headings that end a block, and the closer fallback. A body keeps its own
320
+ lines' indentation. CommonMark allows three spaces; this allows any, because a
321
+ model that indents an emission indents all of it (operator, 2026-09-12: measured
322
+ at five to ten percent of emissions on GLM-5.3-flash).
323
+
324
+ §inline-chain A closer on a heading line, or on a body's closing line, may be
325
+ followed on that same line by the next opener; the closer still closes, and the
326
+ opener opens. This absorbs the habit of writing several operations in one
327
+ paragraph after prose. Prose after a closer on its line ends the chain; slot-shaped
328
+ text there is the heading's own and is read under {§transparent-inline-closer}.
329
+
330
+ §transparent-inline-closer **A closer mid-heading is read as if it were not written.** A
331
+ closing fence on a heading line followed by more of that heading — a `<scope>`, an
332
+ `[option block]`, a `<!-- aside -->`, or a naked matcher — does not end the reading: the
333
+ heading keeps taking its slots under {§trailing-slots} and {§naked-pattern}, exactly as though
334
+ the closer were absent, so ```READ (a.md)``` `<1,2>` is the same operation as `READ (a.md) <1,2>`.
335
+ The closer is still a closer: the block ends with that physical line and never reaches down for
336
+ the next operation, which is what a bare heading carrying a matcher would do. A closer followed
337
+ by the next opener is {§inline-chain}, and by nothing is the ordinary {§fence-closer}. There is
338
+ no ambiguity to resolve: a slot begins with `<` or `[` and an opener with a backtick run, so the
339
+ shapes are disjoint (operator, 2026-09-13: "If there is no risk of ambiguity, then we add
340
+ tolerance. Turning model soup into operations instead of errors is a cardinal imperative").
341
+
342
+ §executor-case **An executor tag in any case.** A fence tag that matches a
343
+ registered executor's name case-insensitively opens that executor (`SH` opens
344
+ `sh`), and the statement's `executor` is the registered spelling, so a lookup
345
+ by that name never misses. Operation names stay uppercase by teaching and were
346
+ never written otherwise in 12,000 fences; one `SH` in 1,300 `sh` fences was
347
+ (2026-09-13 census). An unregistered name in any case is still prose
348
+ ({§interstitial-fence}).
349
+
350
+ §one-line-turn **A whole turn on one line.** The most frequent private rejection
351
+ across the 2026-09-12/13 dumbox runs (five of eleven) was a turn emitted as a
352
+ single line: prose, then heading after heading with no line ending anywhere. Two
353
+ rules absorb it. The next opener on a heading's own line, after the heading's
354
+ slots, ends that heading's block bodyless and opens ({§empty-section}), so
355
+ `````EDIT (a.rs) <60,66> <!-- drop --> ````EDIT (b.rs) <31,38>` is two scoped
356
+ deletions; and a TASK whose inventory rides its heading line as a `[…]` block
357
+ (`````TASK [{"content": "…", "status": "in_progress"}] ````) takes that block as
358
+ its body when nothing sits beneath the heading, with one warning-severity
359
+ advisory naming the body as where the inventory belongs. A block beneath the
360
+ heading still wins.
361
+
362
+ §anchor-digits In a text scope, `@` followed by one to four digits cannot be a
363
+ hash and is read as that line number, with one warning-severity advisory naming
364
+ the five-character anchor form. Five characters after `@` are always an anchor.
365
+
366
+ §unclosed-aside A heading whose aside opens with `<!--` and never closes on its
367
+ line takes the rest of the line as the aside, with one warning-severity advisory.
368
+ A closed aside followed by more text is unchanged.
369
+
370
+ §interstitial-fence A fence line that names no native operation and no known
371
+ executor opens nothing: unlabeled, or tagged like a code block (`ts`, `json`),
372
+ outside a block it is prose and ignored like every other outside line
373
+ ({§whitespace-contract}); inside a body it is body. Nothing is promoted into a
374
+ header or recursively parsed. There is no implicit SEND: a reply is an explicit
375
+ `SEND` block. (This replaces the retired unlabeled-fence SEND of the fences
376
+ chapter, whose unlabeled fences turned displaced headings into silent messages.)
377
+
378
+ §bare-heading-advisory An operation name that opens a line outside any block in the
379
+ shape of a heading (`READ (…)`, `TASK`, …) is prose and runs nothing. The parser
380
+ emits one warning-severity advisory naming the fence form, placed after the parsed
381
+ items, so the loss is never quiet.
382
+
383
+ §empty-section Both the compact bodyless form and an empty multiline block
384
+ normalize optional bodies to null. TASK normalizes an empty body to `[]`
385
+ under {§plan-value}. Closing fences are conventional, never required
386
+ ({§closer-fallback}).
387
+
388
+ §statement-rendering `PlurnkParser.stringify` renders native OP names and named
389
+ runtime fences from the shared AST, with one blank line between operations.
390
+ Every closing fence occupies its own line, including bodyless operations;
391
+ inline fences remain accepted input, not generated examples.
392
+ It chooses at least four backticks and more than any run within the body, and a
393
+ numeric delimiter whenever the body holds a heading line of four or more backticks
394
+ ({§fence-heading-in-body}), preserving body bytes on reparse. Fence length and
395
+ delimiter are syntax, not AST or persistence state. Core-authored programs use
396
+ this serializer and the ordinary admission parser.
397
+
398
+ | Element | Contract |
399
+ |---|---|
400
+ | Fence name | Reserved native OP, otherwise a registered executor or attached MCP service |
401
+ | `(path)` | Target/program/tool slot; COPY and MOVE each have two resource operands |
402
+ | `[metadata]` | One JSON array of option objects, owner-interpreted; options, never the op's input |
403
+ | `<scope>` | Operation-specific numeric or anchored coordinates |
404
+ | `<!-- … -->` | Optional final, single-line aside |
405
+ | Body | Literal content between framing newlines |
406
+ | Closing fence | The opening backtick count and delimiter, on its own line |
407
+
408
+ §slot-order Producers put target, scope, metadata, then aside, separated
409
+ by one ASCII space. Target and scope form one resource selection; COPY/MOVE
410
+ repeat the complete selection/metadata group per operand. ANTLR accepts
411
+ adjacent slots and scope/metadata permutations within a selection without
412
+ changing ownership or making them distinct canonical forms. Each selection
413
+ has at most one scope; its metadata blocks retain their authored order.
414
+
415
+ §plan-slotless TASK accepts no target or metadata. Its optional scope carries
416
+ waiting timing; its inventory body begins below the header.
417
+
418
+ §heading-inline-body Nonempty body text belongs below the fence header.
419
+ The ingester tolerates body text after horizontal whitespace on the header,
420
+ preserves it, and emits one warning stating that normalization. This does not
421
+ change the meaning of a compact empty block or permit unmatched fences.
422
+
423
+ §operation-aside The final header modifier may be one single-line HTML
424
+ comment. AstBuilder removes its delimiters and surrounding whitespace into
425
+ `aside: string | null`. It is durable descriptive text, not authority,
426
+ routing, timing, or body input. Comments inside a body remain literal except
427
+ for the narrowly owned {§misplaced-aside-advisory}.
428
+
429
+ §scheme-metadata-modifier A target may carry one single-line `[metadata]`
430
+ block after its scope; an executor fence also admits it without a target.
431
+ Read with its brackets, the block is a JSON array of option objects, merged
432
+ left to right with later keys winning; the keys belong to the selected scheme
433
+ or executor, which owns interpretation, validation and authority. The language
434
+ assigns no meaning to the content and stores each block's exact inner text:
435
+ balanced brackets inside the block are retained, and double-quoted strings
436
+ protect their brackets. Brackets inside `(path)` remain ordinary path and
437
+ glob characters. A block that is not valid JSON, or a second block on one
438
+ operand, is the owner's `400`, never a parser diagnostic. An unfinished block
439
+ or multiline metadata loses its boundary. Two keys never reach an owner:
440
+ `pattern`, the language's own ({§matcher-option}), and `env`, reserved for the
441
+ service's environment option on the operations that open a process or a
442
+ Worker; the shared reader withholds both from the owner's options.
443
+
444
+ §matcher-option **`pattern` is the matcher, and it lives in the heading.** On
445
+ FIND, READ, KILL, EDIT, and each COPY/MOVE operand, the option
446
+ `[{"pattern": "<matcher>"}]` carries the matcher string exactly as a body once
447
+ did: the leading prefix claims its dialect under {§matcher-prefix-claims}, and
448
+ AstBuilder lifts it into the statement's `matcher` (`MatcherBody | null`),
449
+ positioned dialect errors included. A block that carries only `pattern`
450
+ leaves `metadata: null` for the owner; beside other keys the block stays with
451
+ the owner verbatim, and the owner's reader treats `pattern` as reserved. The
452
+ language lifts only from one block that parses as a JSON array of objects;
453
+ anything else lifts nothing and reaches the owner's `400` untouched. A
454
+ `pattern` that is present but not a string is the language's own positioned
455
+ diagnostic, as is a matcher of a claimed dialect that fails admission. FIND,
456
+ READ, and KILL take no body at all: a body beneath their heading is ignored and
457
+ the operation still runs, with one warning-severity advisory naming the
458
+ heading-line form (`FIND takes no body; the body was ignored. A pattern belongs
459
+ on the opening fence line after the path.`); it is never silently read as a
460
+ matcher, and it never strikes ({§matcher-body-redirect}). The heading line itself
461
+ is the matcher's home ({§naked-pattern}). A body that is only an HTML comment is
462
+ still the aside under {§misplaced-aside-advisory}. EDIT keeps its literal body:
463
+ with a matcher it is the replacement for every selected span ({§edit-pattern}),
464
+ and an absent body deletes them. `PlurnkParser.stringify` writes a lifted matcher
465
+ whose block left no metadata back bare when the bare form reads back identically
466
+ ({§naked-pattern}), otherwise as `[{"pattern": "…"}]`.
409
467
 
410
468
  ## 3. Lexical elements
411
469
 
412
- | Element | Accepted shape or role |
413
- |-------------|--------------------------------------------------------------------|
414
- | `OP` | `FIND READ EDIT COPY MOVE SEND EXEC BARE WORK FORK KILL PLAN` |
415
- | `delimiter` | `[A-Za-z0-9_]*`, adjacent to PLAN or OP |
416
- | `(path)` | Local path or scheme URL target; detailed in §5 |
417
- | `{metadata}` | Repeatable opaque scheme modifier attached after a target |
418
- | `<scope>` | One or more signed integers or decimals; detailed in §7 |
419
- | annotation | Optional trailing `<!-- … -->` descriptive text |
420
- | `body` | Opaque section text before the next same-lane heading or EOF |
470
+ | Element | Shape or role |
471
+ |---|---|
472
+ | Native OP | `FIND READ EDIT COPY MOVE SEND BARE WORK FORK KILL TASK` |
473
+ | Executor name | Letters, digits, `_`, `.`, `+`, or `-`; reserved OPs win |
474
+ | Fence | Three or more backticks, matched by exact count |
475
+ | `(path)` | Local path, URI, program or tool name; §5 |
476
+ | `[metadata]` | One JSON array of owner-defined option objects |
477
+ | `<scope>` | Numeric or anchored coordinates; §7 |
478
+ | Body | Literal text; never recursively interpreted as operations |
421
479
 
422
480
  ## §op-shapes 4. Per-operation semantics
423
481
 
@@ -426,83 +484,115 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
426
484
 
427
485
  | OP | `(path)` | `<scope>` | `body` |
428
486
  |------|----------------------------------------------|---------------------------------|--------------------------------|
429
- | PLAN | none | none | required Plurnk Plan JSON array |
430
487
  | FIND | required target or glob | optional result range | optional matcher |
431
488
  | READ | required target | optional text region | empty |
432
489
  | EDIT | required file or entry | required for an existing target | literal text |
433
490
  | COPY | required source and destination | optional region after each path | empty |
434
491
  | MOVE | required source and destination | optional region after each path | empty |
435
- | EXEC | optional `[executor]`, optional program path ({§exec-executor-slot}) | optional timeout, poll | optional program input |
436
- | BARE | none | none | required prompt |
437
- | WORK | required fresh `worker://name` | none | required prompt |
438
- | FORK | required context-inheriting `worker://name` | none | required prompt |
439
- | KILL | required target, including a log item | optional text region ({§kill-scope}) | optional matcher |
440
- | SEND | a label `(NEXT\|WAIT\|TERM\|FAIL)` or an optional recipient ({§send-label}) | optional timeout, poll on WAIT | message; terminal is nonempty |
441
-
442
- §operation-code-polymorphism SEND and KILL share a numeric wire slot, not one universal numeric vocabulary.
443
- For pathless terminal SEND, the code is the loop disposition defined in §9.
444
- Directed SEND and KILL delegate any present code to the addressed target's
445
- operation contract; a live process may interpret a KILL code as a Unix signal,
446
- but that interpretation does not define KILL generally.
447
-
448
- §plan-value **PLAN carries one installment of the model's running work journal.**
449
- Its entries record newly made working-memory items: durable findings and decisions
450
- are `memory`, finished actions are `completed`, open work is `pending`, and active
451
- priorities are `in_progress`. Admission parses the JSON body — one JSON array document in any whitespace layout, including the {§json-result-rendering} spread the log projects — strips unknown
452
- entry keys (the model-facing Plan carries no priority: struck 2026-08-24 so the
453
- log echoes only the canonical shape, starving stale-field habits), and validates
454
- the canonical bare array: every entry has string `content` and `status` in
455
- `pending | in_progress | completed | memory`. A nonempty plain-text,
456
- malformed-JSON, or otherwise invalid body becomes one `in_progress`
457
- entry whose content is the exact authored body; admission performs no partial
458
- repair or list inference. An empty body becomes the planless `[]`
459
- value. Each PLAN is the complete semantic value of that journal installment;
460
- prior installments remain ordinary curatable log items. The exact `turnOps`
461
- source remains forensic program evidence, while the normalized array is the sole
462
- semantic value used by AST, persistence, durable log bodies, and model-packet
463
- materialization. PLAN is public log content—not
464
- provider reasoning—and Plurnk initially mints no `_meta` values. Dispatch records
465
- the canonical value and has no other runtime effect.
492
+ | execution | the fence name is the runtime; optional program/tool path ({§exec-executor-slot}) | optional timeout, poll | optional program input |
493
+ | BARE | optional prompt resource | none | prompt; optional with a path |
494
+ | WORK | optional fresh `worker://name`, or a prompt resource ({§worker-spawn-prompt-resource}) | none | prompt; optional with a resource |
495
+ | FORK | optional context-inheriting `worker://name`, or a prompt resource | none | prompt; optional with a resource |
496
+ | KILL | required target, including a log item | optional text region ({§kill-scope}) | none; the matcher is the `pattern` option |
497
+ | SEND | optional recipient | optional recipient timing | message |
498
+ | TASK | none | optional timeout and poll for waiting intent | Plurnk Plan JSON array |
499
+
500
+ §operation-code-polymorphism Operation-result statuses and turn dispositions are
501
+ distinct facts. TASK derives lifecycle intent from its inventory;
502
+ SEND and KILL carry no disposition operand.
503
+
504
+ §plan-value **TASK carries the complete current task inventory.** Admission
505
+ parses one JSON array in any whitespace layout, including
506
+ {§json-result-rendering}, strips unknown entry keys, and validates string
507
+ `content` and native `status`. Opaque `_meta` remains optional. Nonempty plain
508
+ text, malformed JSON, or an invalid array becomes one `in_progress` entry
509
+ containing the exact body, with one factual warning. No partial repair or list
510
+ inference occurs. A blank body becomes `[]`, never inferred completion.
511
+ The normalized array is the sole semantic value in AST, persistence and model
512
+ log; exact authored bytes remain in `turnOps`. Earlier inventories are history,
513
+ not accumulated obligations. Task descriptions are not executable dependencies.
514
+
515
+ §task-inventory-intent The first matching row determines intent, independently
516
+ of entry order. Actual execution adjudicates intent under {§wait-obligation-matrix}.
517
+
518
+ | Inventory condition | Intent | Derived lifecycle status |
519
+ |---|---|---|
520
+ | TASK omitted | Continue silently | 102 |
521
+ | Explicit empty inventory | Recover empty inventory | 102 |
522
+ | Any `in_progress` | Continue independent actionable work | 102 |
523
+ | Any `waiting`, no `in_progress` | Await work or an event | 202 |
524
+ | Any `todo`, no actionable or waiting entry | Continue | 102 |
525
+ | All terminal, any `completed` | End successfully | 200 |
526
+ | All `failed`, nonempty | End unsuccessfully | 499 |
527
+
528
+ `todo` is not started; `in_progress` can be actively advanced;
529
+ `waiting` awaits an ongoing stream, worker or external event. `completed` is
530
+ successful resolution; `failed` is unsuccessful resolution. A failed sibling
531
+ does not terminate independent unfinished work. The engine does not infer a
532
+ dependency graph from task text.
466
533
 
467
534
  §plan-acp-projection **Only an ACP-facing boundary projects the model-native
468
535
  Plan.** It constructs ACP's `{ "entries": [...] }` Plan object from the internal
469
536
  array, synthesizes the ACP-required neutral `medium` priority on every entry
470
- (the model-native Plan carries none), maps each `memory` entry to ACP
471
- `completed`, and prefixes its content with exact "Memory: " framing without
472
- duplicating an existing prefix. Every other entry field remains unchanged, and
473
- the internal value is not mutated.
537
+ (the model-native Plan carries none). The internal value is never mutated.
538
+ Native `todo` maps to ACP `pending`: the same state under ACP's name, so it carries no marker.
539
+ Native `waiting` maps to ACP `in_progress`, with `Waiting:` and a space prepended
540
+ to its display content; native `failed` maps to ACP `completed`, with `Failed:` and a space.
541
+ Both carry their native status in `_meta["plurnk.xyz/status"]`, an edge-owned
542
+ key derived from the native status rather than trusted from authored metadata.
543
+ Other statuses and unrelated metadata remain unchanged. The labels preserve
544
+ meaning even when a generic client ignores extension metadata.
474
545
  The projected value validates against the separately owned ACP Plan schema pinned
475
546
  to ACP v1
476
547
  [`schema-v1.21.0`](https://github.com/agentclientprotocol/agent-client-protocol/tree/schema-v1.21.0)
477
548
  commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
478
549
 
479
- §exec-executor-slot An EXEC heading takes an optional `[executor]` slot before its path: `### EXEC0 [python3] (tools/report.py)`. The executor may also trail the path (`### EXEC0 (tools/report.py) [python3]`); either position binds the same AST, since no other slot after a path uses `[...]`. Canonical rendering leads with the executor. Two executors are the one rejected shape. The bracket names the registered executor that runs the program — a tool family, a language runtime, or the shell — and lexes as one `EXECUTOR` token only on an EXEC heading. The path names the program: a registered tool of that family, or a script file or entry. A bare `### EXEC0` is the shell running its body; with a path the body is the program's input. The AST carries `executor` (null for the shell) and `target` separately; the path is never split. Each of the executor, the path, and the `<timeout,poll>` scope appears at most once. Tool teaching, not the grammar, spells the registered executors; a `{cwd=…}` metadata block, interpreted by the executor, names the working directory.
480
-
481
- §send-label SEND's path slot carries either a turn label or a recipient. The four
482
- labels `(NEXT)`, `(WAIT)`, `(TERM)`, and `(FAIL)` lex as one `SEND_LABEL` token
483
- and make the SEND terminal: the AST `status` is 102, 202, 200, or 499 and `target`
484
- is null. A label SEND names no recipient; a label beside a recipient path is one
485
- error at the heading naming that rule. A SEND whose path is a recipient
486
- (`### SEND0 (worker://recheck)`, `(https://…)`, `(a2a://…)`), or whose path slot is
487
- empty (the user), is a mid-turn message with `status` null. The GBNF rail spells a
488
- mid-turn recipient as a URL, so a constrained turn can never place a label mid-turn.
489
-
490
- §send-wait-scope A `(WAIT)` SEND keeps its numeric `<scope>` — the park interval
491
- and poll ({§park-202-only}); the dispatcher owns what it accepts. Every other label
492
- takes no scope.
550
+ §exec-executor-slot The fence name selects the executor directly: for example,
551
+ `python3 (tools/report.py)` or `gitea (issue_list)` on the opening fence line.
552
+ Reserved native OP names take precedence. Any other name is an executor fence: its
553
+ AST carries the `runtime` tag and no operation keyword, then `target`, metadata, timing and body fields.
554
+ Registration is checked by the runtime, not by the syntax parser. An attached
555
+ MCP service uses that executor path and its owner validates the named tool and
556
+ input-body JSON against its schema. Unknown names do not fall back to a shell.
557
+ There is no runtime-less form: every execution names its runtime, and canonical
558
+ shell examples name `sh` explicitly.
559
+ The path names a program or tool and is never split. Metadata such as
560
+ `[{"cwd": "…"}]` remains interpreted by the selected executor.
561
+
562
+ §turn-disposition TASK is the sole workflow declaration. `TurnDisposition`
563
+ derives intent from its canonical inventory under {§task-inventory-intent}.
564
+ The AST has no independently settable lifecycle status, target or metadata.
565
+ SEND deliberately messages its recipient, or the user when targetless; it
566
+ neither changes task status nor terminates a run. A program contains one final
567
+ TASK, not last-wins competing inventories. Former lifecycle names are not aliases.
568
+
569
+ §send-wait-scope TASK accepts `<timeout[,poll]>` in whole minutes. It applies
570
+ only to a waiting intent ({§park-202-only}); the dispatcher validates its bounds.
571
+ Otherwise it is unused, with a factual warning rather than a changed outcome.
572
+
573
+ §send-directed-scope A recipient SEND preserves an optional numeric scope after
574
+ its target and metadata. The addressed owner assigns its semantics; worker
575
+ actors use `<delay[,interval]>` ({§worker-scheduled-send}). A targetless message
576
+ takes no scope. Scheduling does not change the message body or disposition.
493
577
 
494
578
  §kill-scope KILL takes an optional text-coordinate scope beside its target, numeric or
495
- anchored (`### KILL0 (log:///**/READ) <17,-1>`, `### KILL0 (worker:///notes.md)
496
- <@aB3dE,@0Aa9Z>`), and an optional one-line matcher body that selects rows. The AST
497
- is `{ op: "KILL", target, lineMarker: TextLineMarker | null, body: MatcherBody | null }`.
579
+ anchored (```` ```KILL (log:///**/READ) <17,-1>``` ```` or
580
+ ```` ```KILL (worker:///notes.md) <@aB3dE,@0Aa9Z>``` ````), and an optional matcher option that
581
+ selects rows or lines (```` ```KILL (log:///**) [{"pattern": "~stale"}]``` ````, {§matcher-option}).
582
+ The AST is `{ op: "KILL", target, lineMarker: TextLineMarker | null, matcher: MatcherBody | null, body: null }`.
498
583
  Without a scope, KILL retires or deletes the whole target; with one, it removes exactly
499
584
  that span — of a log body's packet projection or of an entry's content. Core owns the
500
585
  one-way semantics: there is no operation that restores a scoped-away log body.
501
586
 
502
- §legacy-bracket-slot The bracket slot carries no signal, tag, code, or status on any heading; EXEC alone takes `[executor]` ({§exec-executor-slot}). A `[` on any other heading is one bounded lexer diagnostic that names that rule and the OP's own `(path)` slot, and after PLAN states that PLAN takes no modifiers. The statement drops and its siblings run.
587
+ §legacy-bracket-slot Brackets are the metadata modifier, never an executor
588
+ selector: the runtime or MCP service is the fence name, and tool input belongs
589
+ in the body. A bracket block that leads an executor fence or follows a target
590
+ is metadata, so a legacy `[node]` selector reaches its owner as metadata text
591
+ and is refused there; a bracket before the target of a non-executor OP is one
592
+ bounded header diagnostic that selects nothing.
503
593
 
504
594
  The `<scope>` slot is optional where admitted and its domain is OP-specific. FIND
505
- scopes ordered results. EXEC and a WAIT SEND scope timing. READ, EDIT, COPY,
595
+ scopes ordered results. Executions and SEND scope owner-defined timing. READ, EDIT, COPY,
506
596
  MOVE, and KILL use one universal text algebra independent of mimetype; a log
507
597
  KILL admits only its one- and two-line forms for canonical log-body visibility:
508
598
 
@@ -525,22 +615,37 @@ four-coordinate region ending after the final code point of `endLine`.
525
615
  Producers never emit that form. Other arities and decimal text coordinates are
526
616
  runtime 416 failures.
527
617
 
528
- §bare-statement **BARE requests one isolated model inference.** Its required
529
- body is the complete prompt: no
530
- target, scope, persistent worker identity, or output-language statement shape
531
- is represented in the AST. Runtime provider selection, batching, accounting,
532
- and observation timing belong to the consuming service.
533
-
534
- §read-find-normalization An authored READ with a nonempty matcher body or a
535
- target path classified as a glob normalizes during AST construction to one
536
- ordinary FIND statement. Target, signals, scope, and matcher are preserved;
537
- FIND's result pagination and projection contract then applies. The canonical
538
- AST retains no parallel matcher-READ mode, and the runtime performs no READ
539
- fan-out.
540
-
541
- §read-exact-target After normalization, READ targets one exact resource (a
542
- local path or scheme URL, with optional `#channel` fragment or
543
- `{header: value}` metadata) and has no matcher body. A `<scope>` on READ selects
618
+ §bare-statement **BARE requests one isolated model inference.** Its optional
619
+ path names a prompt resource; its body supplies inline prompt text. At least
620
+ one must supply nonempty text at execution. With both, the complete resource
621
+ text precedes the body, separated by two newlines. The target's scheme owns any
622
+ metadata modifier. No scope, persistent worker identity, or output-language
623
+ shape is represented. Provider selection, source admission, batching,
624
+ accounting, and observation timing belong to the consuming service.
625
+
626
+ §read-find-normalization An authored READ is never rewritten into a FIND. A
627
+ glob target on READ keeps its glob, and the runtime fans it out into one exact
628
+ READ per matching path, with the authored scope and matcher ({§read-fan-out}
629
+ in the core SPEC; operator, 2026-09-13: "give it what it asked for" — a model
630
+ that asks to read every file under a glob gets those files, bounded by the
631
+ FIND page and the preview scope, not a catalog it did not ask for). A matcher
632
+ never changes the operation either: READ with a `pattern` on an exact target
633
+ stays READ and renders the selected lines ({§read-pattern}). The survey of
634
+ paths is FIND, and only FIND.
635
+
636
+ §local-path-fragment **A bare path takes `#channel` like a URL.** `data/users.html#readable`
637
+ parses as `{ kind: "local", raw: "data/users.html", fragment: "readable" }`: the first `#` ends
638
+ the path and the rest is the channel (a spelling that opens with `#` names no path and stays
639
+ whole), exactly as `worker:///a.html#readable` decomposes, so
640
+ the `#channel` a READ receipt advertises (`channels: {"#readable": N}`) is addressable in the
641
+ same bare spelling the receipt used (2026-09-13 dumbox demo: the model appended it and was
642
+ told no entry existed at `users.html#readable`). `raw` is therefore always the path alone; a
643
+ bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
644
+ Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
645
+
646
+ §read-exact-target READ targets one exact resource (a local path or scheme
647
+ URL, with optional `#channel` fragment or `[metadata]`) and has no body. A
648
+ `<scope>` on READ selects
544
649
  a text region from that exact target. Without a scope, READ defaults to
545
650
  `<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope components are
546
651
  invalid on READ.
@@ -554,7 +659,10 @@ Mutation semantics:
554
659
  - `<0>` prepends and `<-1>` appends.
555
660
  - §empty-mutation-scope Empty mutation content has one writable position: `<0>`, `<1>`, `<-1>`, and `<1,-1>` all insert the body as its complete value. Other scopes resolve against that same empty value through the ordinary coordinate algebra.
556
661
  - `<SL,SC,EL,EC>` deletes the exact exclusive-end region and inserts the body at its start.
557
- - §transfer-resource-selections COPY and MOVE require two singular `ResourceSelection` operands on the heading, source first and destination second, and admit no body. Each operand consists of `(path)`, any following `{metadata}`, and an optional following `<scope>`; modifiers bind only to the immediately preceding path. The two operands independently select their resource, channel, scheme metadata, and text region.
662
+ - §transfer-resource-selections COPY and MOVE require two singular
663
+ `ResourceSelection` operands, source first and destination second, and admit
664
+ no body. Each selection binds its own target, scope, matcher, and metadata
665
+ under {§slot-order}, {§matcher-option}, and {§scheme-metadata-modifier}.
558
666
 
559
667
  ### §operation-observation Per-operation observations
560
668
 
@@ -566,16 +674,16 @@ Mutation semantics:
566
674
  | COPY | Source and destination selections plus ordered destination effects |
567
675
  | MOVE | Source and destination selections plus ordered destination and source effects |
568
676
  | SEND | Status and recipient acknowledgement when applicable |
569
- | EXEC | Spawn acknowledgement; output arrives through named stream channels |
677
+ | execution | Spawn acknowledgement; output arrives through named stream channels |
570
678
  | BARE | The one-shot model response |
571
679
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
572
680
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
573
681
  | KILL | Status of deletion or termination |
574
- | PLAN | Status of durable complete-Plan logging |
682
+ | TASK | Current inventory and adjudicated lifecycle outcome |
575
683
 
576
684
  §find-result-unit For FIND, authored target shape fixes the paginated result
577
685
  unit. An exact target with a matcher pages flat match locations; a glob or
578
- folder target, and every body-less FIND, pages resources. Resolving a glob to
686
+ folder target, and every matcher-less FIND, pages resources. Resolving a glob to
579
687
  one resource does not make it exact. The same `<N>`, inclusive `<N,M>`,
580
688
  markerless `<1,16>`, and explicit-all `<1,-1>` forms apply to either unit.
581
689
 
@@ -588,8 +696,8 @@ EDIT. Whole-channel transfers remain bodyless structural effects; runtime
588
696
  owners reject binary markers rather than treating a text field as a byte lane.
589
697
 
590
698
  Every operation returns the runtime-neutral `OperationResult` defined by
591
- {§operation-result}. Its `status` belongs to the result envelope and is not a
592
- SEND signal. Durable operation observations are projected into a later packet;
699
+ {§operation-result}. Its `status` belongs to the result envelope; TASK supplies
700
+ the authored lifecycle intent. Durable operation observations are projected into a later packet;
593
701
  retrieval never returns inline within the emitting turn.
594
702
 
595
703
  ## §path-syntax 5. Target and path grammar
@@ -599,7 +707,7 @@ and path globs share the slot; content matchers belong in the body.
599
707
 
600
708
  | Form | Typed admission | Runtime meaning |
601
709
  |-------------------------|---------------------------------------------------------------------|------------------------------------------------------|
602
- | Bare path | `LocalPath { kind: "local", raw }` | Resolves through the runtime's file surface |
710
+ | Bare path | `LocalPath { kind: "local", raw, fragment? }` | Resolves through the runtime's file surface; `#channel` is `fragment` ({§local-path-fragment}) |
603
711
  | `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
604
712
  | Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
605
713
  | `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
@@ -646,69 +754,69 @@ Matching and folder-scope semantics remain runtime concerns.
646
754
 
647
755
  §worker-name The exported `WORKER_NAME` contract governs names minted for URI
648
756
  authority slots: a lowercase DNS label matching
649
- `[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. `RESERVED_AUTHORITIES` contains the
650
- authority-shaped internal worker names `commons` and `plurnk`, which are
651
- unavailable for minting. `~` is the sole current-worker sigil and falls outside
652
- the mintable alphabet; every matching unreserved value, including `self`, is an
653
- ordinary literal worker name. This is a minting and registry invariant, not an
757
+ `[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. There is no reserved-name list: the
758
+ runtime's own actor is named `_plurnk`, a spelling the predicate never admits,
759
+ and `~` is the sole current-worker sigil, likewise outside the mintable
760
+ alphabet. Every matching value, including `self` and `plurnk`, is an ordinary
761
+ literal worker name. This is a minting and registry invariant, not an
654
762
  ingestion restriction: the parser decomposes arbitrary URL authorities.
655
763
 
656
764
  ## §matcher-prefix-claims 6. Bulk pattern matching
657
765
 
658
- FIND, authored READ, KILL, LOOK, and BUFF accept an optional body matcher.
659
- The lexer preserves the body opaquely; AstBuilder assigns the dialect from its
660
- leading characters, then normalizes matcher-bearing READ to FIND under
661
- {§read-find-normalization}.
662
- A leading prefix claims its dialect. Invalid claimed syntax is a positioned
663
- visitor error and never falls back to glob matching.
766
+ FIND, READ, KILL, EDIT, and the COPY/MOVE operands accept an optional matcher
767
+ through the `pattern` option ({§matcher-option}); the client-tier LOOK still
768
+ carries its matcher as a body. AstBuilder assigns the dialect from the
769
+ matcher's leading characters. A leading prefix claims its dialect. Invalid
770
+ claimed syntax is a positioned visitor error and never falls back to glob
771
+ matching.
664
772
 
665
773
  - §heading-boundary-recovery A column-0 heading is the trustworthy boundary. After a
666
774
  statement-level error the parser discards the rest of that statement and resumes at the
667
- next heading; the turn shape is decided locally (a terminal SEND is recognized by its own
668
- disposition signal, never by a whole-turn alternative), so one malformed heading costs one
669
- diagnostic and every later statement, the terminal SEND included, stands on its own. Any
775
+ next heading; the turn shape is decided locally (a turn disposition is recognized by its own
776
+ token, never by a whole-turn alternative), so one malformed heading costs one
777
+ diagnostic and every later statement, the turn disposition included, stands on its own. Any
670
778
  other second path slot names the one-slot rule.
671
- - §scope-slot-tolerance A line scope written inside a path slot (`### COPY0 (worker:///src.md<2,3>)`)
779
+ - §scope-slot-tolerance A line scope written inside a path slot (```` ```COPY (worker:///src.md<2,3>) ````)
672
780
  is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
673
781
  before a slot's closing paren can only be a scope; every path slot of a statement is repaired
674
782
  the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
675
783
  statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
676
784
  never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
677
785
  - §second-path-slot A second `(path)` on a heading that already closed one is a parser
678
- error at the second paren stating the one-slot rule and that a pattern belongs in the body;
679
- the statement is dropped and its siblings run.
786
+ error at the second paren stating the one-slot rule and that a pattern belongs in the
787
+ `[{"pattern": …}]` option; the statement is dropped and its siblings run.
680
788
 
681
- | Prefix | Dialect | Canonical body | Typed admission | Runtime owner |
789
+ | Prefix | Dialect | Canonical form | Typed admission | Runtime owner |
682
790
  |-----------|----------|--------------------------------------|-----------------------------------|---------------------|
683
791
  | `//` | XPath | `//selector` | XPath 1.0 `xpath.parse()` | Mimetype projection |
684
792
  | `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
793
+ | `^` | Regex | `^pattern`, no slashes or flags | ECMAScript `RegExp` construction | Mimetype projection |
685
794
  | `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
686
- | `~` | Semantic | `~phrase` | Any text after the prefix | Embedding index |
795
+ | `~` | Full-text | `~query` | Single-line raw string | SQLite FTS5 index |
687
796
  | `&` | Graph | `&symbol`, `&<symbol`, or `&>symbol` | Exact shape validation | Symbol index |
688
797
  | none | Glob | Shell glob or literal text | Single-line raw string | Mimetype projection |
689
798
 
690
799
  XPath is classified before regex because its prefix is two slashes. Regex
691
800
  splitting respects escapes and character classes; `\/` represents a literal
692
801
  slash. The AST stores regex `pattern` and `flags`, not a compiled object.
693
- Semantic matchers require no parse step. Graph admission validates its direction
802
+ SQLite validates full-text query expressions at execution. Graph admission validates its direction
694
803
  and non-whitespace symbol before runtime. Every other leading character remains
695
804
  in the fallback glob/literal dialect; `@(...)` is therefore an extglob group and
696
805
  bare `@text` remains literal matcher text. Rendered READ coordinates are
697
- structural output rows, not a reserved matcher prefix. Scope carries semantic
698
- threshold and result-range information rather than changing the matcher body.
806
+ structural output rows, not a reserved matcher prefix. FIND scope selects result
807
+ positions without changing the matcher.
699
808
 
700
809
  AstBuilder validation is compile-only and never evaluates a document. Matcher
701
- evaluation belongs to the runtime's selected mimetype, embedding, or symbol
810
+ evaluation belongs to the runtime's selected mimetype, FTS5, or symbol
702
811
  implementation. A matcher admission error is local to its statement; later
703
812
  statements remain recoverable when their boundaries are trustworthy.
704
813
 
705
- - §pattern-body-single-line Every matcher body is one physical line. AstBuilder
706
- rejects multiline bodies before dialect classification, while GBNF excludes
707
- line terminators. A regex that matches a newline uses the two-character `\n`
708
- escape. Non-matcher operation bodies remain multiline.
709
- - §pattern-body-leading-colon The GBNF rail forbids `:` as the first matcher
710
- character. Empty matchers and later colons remain valid; a regex such as
711
- `/^:needle/` expresses a pattern beginning with a literal colon.
814
+ - §pattern-body-single-line Every matcher is one physical line. On the protocol
815
+ operations it is the heading line's text ({§naked-pattern}) or the `pattern` option's
816
+ JSON string ({§matcher-option}), one line by construction; the client-tier LOOK still carries its matcher as a body, and AstBuilder
817
+ rejects a multiline one before dialect classification. A regex that matches a
818
+ newline uses the two-character `\n` escape. Non-matcher operation bodies remain
819
+ multiline.
712
820
 
713
821
  ## §scope-slot 7. Scope markers
714
822
 
@@ -728,15 +836,15 @@ The operation column names the canonical AST operation after
728
836
  | COPY/MOVE source | 0/1/2/4 text coordinates | Region copied or moved from the selected source |
729
837
  | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
730
838
  | KILL | 0/1/2 text coordinates | Whole target when absent; one physical line or inclusive range when present ({§kill-scope}) |
731
- | EXEC | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
732
- | `### SEND0 (WAIT)` | `timeout[,poll]` | Bounded or indefinite wait and optional poll cadence ({§send-wait-scope}) |
839
+ | execution | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
840
+ | ```` ```TASK ```` | `timeout[,poll]` | Waiting intent: bounded or indefinite wait and optional poll cadence ({§send-wait-scope}) |
841
+ | Directed SEND | Owner-defined numeric scope | Worker actors schedule a task with `delay[,interval]` ({§send-directed-scope}) |
733
842
 
734
843
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
735
844
  whole line, two integers are an inclusive whole-line range, and four integers
736
845
  are an exact start-inclusive/end-exclusive region. Mutation scopes additionally
737
- admit `0` as prepend and `-1` as append. A leading decimal on semantic FIND
738
- is a similarity threshold; any remaining integers select result positions. READ
739
- does not admit decimal scope components. A log KILL intersects a valid body-relative line
846
+ admit `0` as prepend and `-1` as append. FIND result positions and READ
847
+ text coordinates do not admit decimal scope components. A log KILL intersects a valid body-relative line
740
848
  scope with each selected body; an absent line is a successful no-op for that
741
849
  body, while unsupported arity is a runtime failure.
742
850
 
@@ -765,95 +873,74 @@ reinterpreting them. FIND owns a deterministic result order so the same
765
873
  inclusive range selects the same positions from unchanged state. The parser
766
874
  does not enforce either condition.
767
875
 
768
- ## §delimiter-discipline 8. Delimiter Discipline
769
-
770
- The delimiter is a turn-wide heading lane. A heading carrying the active lane
771
- is structural; an otherwise valid PLURNK heading carrying another lane is body
772
- text. The lane therefore makes literal or nested PLURNK unambiguous.
876
+ ## 8. Literal programs and code blocks
773
877
 
774
- Delimiter rules:
878
+ A producer carrying literal fences uses an outer backtick count absent from
879
+ standalone fence lines in its body ({§fence-boundary}). The serializer chooses
880
+ a count greater than every backtick run in the body; parsed AST values carry
881
+ no framing state.
775
882
 
776
- - `delimiter` is `[A-Za-z0-9_]*`, concatenated to PLAN or OP with no separator.
777
- - The H1 PLAN establishes the lane; every real H2 operation heading in that
778
- turn has the exact same delimiter.
779
- - An empty delimiter is accepted only by ANTLR ingestion. Canonical teaching and
780
- the generated rail use `0` on PLAN and every operation.
781
- - A body may contain any heading whose delimiter differs from the active lane.
782
- - To carry a nested turn written with lane `0`, choose another delimiter for the
783
- outer turn and repeat it on every outer heading.
784
- - The GBNF deliberately emits only lane `0`. It cannot emit body content that
785
- contains a same-lane structural heading; unconstrained producers use another
786
- outer lane when that representation is required.
883
+ `````text
884
+ ````EDIT (README.md) <1,-1>
885
+ Run the tests:
787
886
 
788
- Example — a lane-0 turn stored inside a lane-2 EDIT body:
789
-
790
- ```example
791
- ## PLAN2
792
- [{"content":"Store the quoted turn.","status":"in_progress"}]
793
-
794
- ### EDIT2 (worker:///quoted.plurnk)
795
- ## PLAN0
796
- [{"content":"Answer from memory.","status":"in_progress"}]
797
-
798
- ### SEND0 (TERM)
799
- Paris.
800
-
801
- ### SEND2 (TERM)
802
- Stored the quoted turn.
887
+ ```sh
888
+ npm test
803
889
  ```
890
+ ````
891
+ `````
804
892
 
805
- The lane-0 headings are ordinary EDIT body text because the outer turn's
806
- structural lane is `2`. This rule belongs to section framing and applies to
807
- every operation, not to EDIT semantics.
893
+ The inner shell example is EDIT content, not an execution. The same
894
+ rule protects code examples in SEND, WORK, FORK, BARE and every other body.
808
895
 
809
- ## 9. SEND Codes
896
+ ## 9. Turn dispositions
810
897
 
811
- Pathless terminal SEND disposition codes align with HTTP semantics so that model training
812
- transfers directly:
898
+ TASK inventory intent maps to the existing HTTP-shaped lifecycle statuses
899
+ ({§task-inventory-intent}). The runtime adjudicates that intent against actual
900
+ results, obligations and timing:
813
901
 
814
- | Class | Terminal meaning | Disposition used by the model |
815
- |-------|-----------------------------------------------------------------|-------------------------------|
816
- | `1xx` | Continue after submitted operations | `102 Processing` |
817
- | `2xx` | Conclude successfully or wait on live obligations | `200 OK`, `202 Accepted` |
818
- | `4xx` | Abandon the loop after a model-side inability | `499` |
819
- | `5xx` | Runtime or infrastructure failure; never a model terminal claim | none |
902
+ | Intent | Nominal status | Meaning |
903
+ |---|---|---|
904
+ | TASK omitted | 102 | Continue silently, without a receipt or strike for omission |
905
+ | empty, continue, todo | 102 | Continue or recover; an explicit empty inventory is refused with a soft 409 receipt, no strike |
906
+ | wait | 202 | Park when a live obligation or explicit timing exists |
907
+ | complete | 200 | Conclude once execution results permit completion |
908
+ | fail | 499 | End unsuccessfully and cancel unresolved descendant scope |
909
+ | Runtime or infrastructure failure | 5xx | Not a model-authored task status |
820
910
 
821
911
  ### §waitpid-dispositions The terminal contract (waitpid)
822
912
 
823
- The model signals one intention per turn — **continue (102)**, **done
824
- (200)**, **wait (202)**, or **give up (499)** — and the engine verifies
825
- the claim against the loop's live obligations (spawned children, open
826
- streams, pending retrievals); the grammar polices *shape* only. Asking
827
- the human is the native `question` EXEC tool ({§question-tool}), not a
913
+ The model may supply one current inventory per turn; its statuses determine
914
+ one intention. Without TASK, an operation-bearing turn continues silently.
915
+ The engine verifies an explicit intention against the loop's actual
916
+ obligations (spawned children, open streams, pending results); the grammar
917
+ polices *shape* only. Asking
918
+ the human is the native `question` executor tool ({§question-tool}), not a
828
919
  disposition. The shape rules ARE structural:
829
920
 
830
- - §send-mid-reservation The four labels lex as one `SEND_LABEL` token
831
- ({§send-label}), making a label SEND **structurally terminal**: a
832
- statement after it is a parse error (the mid-termination rule), and the
833
- GBNF spells mid-position SEND recipients as URLs, which no label is.
834
- This keeps the grammar's last-SEND model and the dispatcher's
835
- first-label model coincident.
836
- - A **mid** SEND (before the terminal) is comms: a recipient path or
837
- none, no label, empty body allowed.
838
- - §terminal-body-nonempty The GBNF rail requires a non-empty terminal SEND body — a constrained
839
- turn cannot end empty-handed. ANTLR remains tolerant during ingestion.
840
- - §park-202-only The **park** rides `(WAIT)` only: `<T>` (wait up to T minutes),
841
- `<T,P>` (adds a poll cadence, mirroring EXEC's slot), `<-1>`
842
- (indefinite; the join's own liveness bounds it). See §7 for the
843
- GBNF-strict / ANTLR-tolerant split.
844
- - §no-idle-102 A **zero-statement turn may not conclude `(NEXT)`** — "continue"
845
- with nothing submitted is a spin. The GBNF's `tail-0` exits through
846
- a terminal trie without the `(NEXT)` tail, so the idle turn (`PLAN`
847
- straight into `### SEND0 (NEXT)`) is unemittable; one statement restores
848
- the full label set. The other three stay legal bare (a zero-op
849
- `(WAIT)` is the engine's obligation check). ANTLR stays tolerant
850
- (ingest side). A dispatch-emptied turn — ops emitted but failing
851
- downstream validation — survives the rail by nature; the engine's
852
- idle-turn 409 backstops that class.
853
-
854
- SEND with no `(path)` broadcasts to the default control channel — the
855
- turn's disposition. SEND with `(path)` directs the message at a
856
- specific recipient URI (a worker, a stream, a peer).
921
+ - §send-mid-reservation TASK has a reserved token ({§turn-disposition}).
922
+ A turn admits at most one TASK, anywhere among its operations
923
+ ({§disposition-anywhere}); the runtime executes it last. A second
924
+ disposition is a structural error, not a choice between competing outcomes.
925
+ - §disposition-anywhere The disposition may sit anywhere in a model turn
926
+ (operator, 2026-09-12: models state the plan first; the inventory is a
927
+ statement about state, not a boundary). `PlurnkParser.parse` admits every
928
+ operation before and after it in authored order; the runtime defers only the
929
+ disposition until the other admitted operations settle
930
+ ({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
931
+ position. TASK omission does not synthesize a disposition ({§turn-shape}).
932
+ - SEND is communication: an optional recipient path and an optional body.
933
+ - §park-202-only TASK wait intent applies `<T>` (wait up to T minutes),
934
+ `<T,P>` (adds a poll cadence, mirroring the execution slot), `<-1>`
935
+ (indefinite; the join's own liveness bounds it). See §7 for the scope
936
+ slot's shape. Other intents leave timing unapplied
937
+ with a factual warning; timing does not override the inventory's intent.
938
+ - §inventory-only-turn A TASK-only turn is valid for every inventory intent.
939
+ Actionable work does not require an invented OP and does not imply parking.
940
+ Ordinary repetition, strike and execution limits still apply.
941
+
942
+ SEND with no `(path)` responds to the Active Prompts without ending the turn. SEND with
943
+ `(path)` directs the message to that recipient. Neither changes loop status.
857
944
 
858
945
  ### §send-body SEND body projection
859
946
 
@@ -865,78 +952,42 @@ defines no synthetic scheme or READ-back convention for them.
865
952
 
866
953
  ## §parser-architecture 10. Parser architecture
867
954
 
868
- `plurnkLexer.g4` owns tokens and modes; `plurnkParser.g4` owns document tiers
869
- and statement composition; AstBuilder projects parse-tree leaves into the public
870
- AST. Generated TypeScript targets the `antlr4ng` runtime.
955
+ The implementation this section describes lives in `@plurnk/plurnk-parser`
956
+ ({§parser-boundary}); this section remains the contract it implements.
957
+
958
+ ANTLR owns framing, slots and statement composition; AstBuilder produces the
959
+ schema-owned AST. Registration, effects and authority remain runtime concerns.
871
960
 
872
961
  ```mermaid
873
962
  stateDiagram-v2
874
963
  [*] --> DEFAULT
875
- DEFAULT --> DEFAULT: whitespace or TEXT
876
- DEFAULT --> SLOTS: H1 PLANlane or H2 OPlane
877
- SLOTS --> SIGNAL: signal opener
878
- SIGNAL --> SLOTS: signal close
879
- SLOTS --> TARGET: target opener
880
- TARGET --> TARGET: balanced literals / target escapes
881
- TARGET --> SLOTS: target close at depth zero
882
- SLOTS --> METADATA: metadata opener after target
883
- METADATA --> METADATA: balanced inner braces
884
- METADATA --> SLOTS: metadata close at depth zero
885
- SLOTS --> SLOTS: scope token
886
- SLOTS --> SLOTS: trailing annotation
887
- SLOTS --> BODY: heading line end
888
- BODY --> DEFAULT: same-lane heading boundary
889
- BODY --> [*]: end of input
964
+ DEFAULT --> SLOTS: fenced native OP or executor
965
+ SLOTS --> TARGET: (
966
+ TARGET --> SLOTS: )
967
+ SLOTS --> METADATA: {
968
+ METADATA --> SLOTS: }
969
+ SLOTS --> BODY: header newline or tolerated inline body
970
+ SLOTS --> DEFAULT: matching compact closer
971
+ BODY --> DEFAULT: matching standalone closer, no nested block
972
+ BODY --> BODY: nested literal block or other body content
890
973
  ```
891
974
 
892
- The first H1 PLAN establishes the turn lane. DEFAULT recognizes only an H1
893
- PLAN or H2 minted operation carrying that exact lane. SLOTS admits
894
- operation-appropriate signal, target-with-metadata, and scope openers in any
895
- order, followed by an optional annotation; the parser grammar enforces
896
- at-most-once slot multiplicity and keeps repeatable metadata attached to its target.
897
- Signal submodes select tags, integer,
898
- or identifier tokens by operation family. TARGET preserves balanced inner
899
- parentheses and recognized target escapes. BODY emits opaque text until a
900
- same-lane heading boundary or EOF.
901
-
902
- A differently delimited heading stays BODY text. Multi-turn logs are plain
903
- sequences of independently lane-anchored PLAN turns. Complete native reasoning
904
- enclosures before PLAN remain one TEXT token so an operation drafted inside
905
- provider reasoning cannot become the turn anchor.
906
-
907
- RecordingListener captures lexer and parser failures; AstBuilder adds visitor
908
- failures. PlurnkErrorStrategy recovers at structural heading boundaries where
909
- possible. EOF is a valid body boundary. An unfinished signal, target, or metadata block produces
910
- `unparsedTail`; no later input is trustworthy.
911
-
912
975
  ## §whitespace-contract 11. Whitespace and interstatement text
913
976
 
914
- | Location | Canonical generation | Tolerant ANTLR ingestion |
915
- |-----------------------------|---------------------------------------|-----------------------------------------------------------|
916
- | Heading marker | `## PLAN0` or `## OP0` at column zero | The initial PLAN may directly follow leading TEXT; subsequent headings retain exact depth and column |
917
- | Between OP and delimiter | Adjacent | Must remain adjacent |
918
- | Before each modifier | One ASCII space | Zero or more horizontal whitespace characters |
919
- | Inside signal | Adjacent values | Horizontal whitespace is ignored; newline is invalid |
920
- | Inside target | Path alias plus target escapes | Balanced literals tolerated; newline is invalid |
921
- | Inside scheme metadata | Scheme-defined single-line content | Balanced braces tolerated; newline is invalid |
922
- | Inside scope | Comma-separated numbers | Dash separator and one post-comma space are also accepted |
923
- | Before annotation | One ASCII space | Zero or more horizontal whitespace characters |
924
- | Inside annotation | One-line prose padded by one space | Any single-line text through the first closing `-->` |
925
- | Inside body | Character-perfect | Character-perfect |
926
- | Between canonical sections | No empty separator line | One empty separator line is also admitted |
927
- | Before the first PLAN | Nothing | Whitespace or TEXT may surface as preamble items without requiring a separator before PLAN |
928
-
929
- PLURNK never escape-decodes body text: `\n` reaches the owning operation as
930
- backslash plus `n`. A matcher or executor may interpret those characters under
931
- its own body dialect. Producers that need a physical newline in literal EDIT
932
- content emit an actual newline.
933
-
934
- `parse` admits TEXT before its PLAN, including without an intervening line
935
- break, and returns it as ordered text items without assigning semantics. Once
936
- a heading begins, all nonstructural text belongs to that section body.
937
- `parseStatements` and `parseClient` admit H2 statements;
938
- `parseLog` admits consecutive H1 PLAN turns. PLURNK defines no general comment
939
- syntax; only the trailing heading position gives `<!-- … -->` annotation meaning.
977
+ Body framing removes the header line ending and the single line ending
978
+ immediately before the closing fence. Every other body character is preserved,
979
+ including leading/trailing blank lines, indentation, CRLF and literal
980
+ backslash escapes. A formatter adds its own framing newline even when a body
981
+ already ends in one. Interstatement whitespace belongs to no body.
982
+
983
+ A header starts at column zero; the first operation may follow provider preamble
984
+ without a separating newline. Text outside operation blocks is ignored in every
985
+ parser tier: before, between, and after operations. It produces no AST item,
986
+ message, receipt, or diagnostic. Exact source remains in `ops:///` under
987
+ {§turn-ops-log-curation}; body bytes and source positions are unchanged. A matching
988
+ closer still ends its body, and no missing closer is inferred. No generic Markdown
989
+ rendering, indentation stripping or recursive code-block extraction occurs.
990
+ Only a header aside has aside semantics.
940
991
 
941
992
  ## §public-api 12. Public API
942
993
 
@@ -945,39 +996,34 @@ and wire types come from generated schemas; the small hand-maintained parser
945
996
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
946
997
  express. Consumers never receive ANTLR parse-tree or token types.
947
998
 
948
- §turn-shape `PlurnkParser.parse` accepts exactly one model turn containing at
949
- least one parsed source operation. Canonical generation may begin with H1 PLAN
950
- (a SHOULD, never repeated mid-turn) and ends with a label H2 SEND. A turn without
951
- a PLAN stands as written — no PLAN is synthesized and nothing is diagnosed. When
952
- no valid terminal SEND was parsed, the parser appends a bodyless `### SEND0 (NEXT)`
953
- carrying {§parser-position} `UNKNOWN_POSITION` and one exact hard diagnostic
954
- stating the observed boundary failure and applied default. The source text
955
- remains unchanged. The GBNF rail takes the same optional PLAN.
956
- An authored terminal SEND still ends the source turn: a same-lane operation or
957
- other hard error after it is outside the trustworthy boundary. Tolerated TEXT
958
- may appear only before the first operation; after that point, nonstructural text
959
- is section body content. `parseLog` remains strict canonical PLAN-through-SEND
960
- input, and GBNF remains strict canonical generation.
961
-
962
- §document-fence `PlurnkParser.parse` additionally admits one outer Markdown code
963
- fence whose opening line is exactly ```` ```example ```` (or the earlier ```` ```plurnk ````) and whose closing line,
964
- when present, is ```` ``` ````. The fence encloses the complete
965
- model turn and projects neither text nor body content into the AST. Its opener
966
- commits the document to either that closer or EOF immediately after the turn.
967
- This is document framing, not another statement grammar, and
968
- no other parser tier admits it. GBNF continues to shape the paired form.
999
+ §turn-shape `PlurnkParser.parse` accepts one model turn. A turn without any
1000
+ operation is reported by one hard diagnostic (`no valid Plurnk operation was
1001
+ found.`), which the host may admit as an empty turn rather than reject
1002
+ (plurnk-core `§empty-turn`). An explicit disposition may sit anywhere in it
1003
+ ({§disposition-anywhere}). Omitted TASK
1004
+ means silent continuation: no synthesized statement, diagnostic, receipt,
1005
+ warning, or strike. The authored operations and source remain unchanged.
1006
+ Explicit empty or malformed inventories retain their own handling.
1007
+ Unfinished blocks never receive inferred closers.
1008
+ Bounded operation errors retain valid siblings. Duplicate dispositions
1009
+ and failed document boundaries remain structural failures.
1010
+
1011
+ `parseLog` reads consecutive saved turns separated by their dispositions and
1012
+ requires their dispositions; a saved turn is stored per turn, so a mid-turn
1013
+ disposition never needs splitting. There is no outer Markdown program wrapper;
1014
+ the executable blocks themselves are the program.
969
1015
 
970
1016
  §tier-entrypoints Each parser entry point owns one document tier:
971
1017
 
972
1018
  | Entry point | Accepted document | Result statement type |
973
1019
  |--------------------------------|----------------------------------------------------------------|-----------------------|
974
- | `PlurnkParser.parse` | One operation-bearing model turn, bare with optional TEXT or inside one outer `example` (or `plurnk`) fence; omitted PLAN/SEND recover to defaults | `PlurnkStatement` |
975
- | `PlurnkParser.parseStatements` | Zero or more protocol statements and hidden whitespace | `PlurnkStatement` |
976
- | `PlurnkParser.parseLog` | One or more consecutive same-lane PLAN-anchored turns | `PlurnkStatement` |
977
- | `PlurnkParser.parseClient` | H2 protocol statements plus read-shaped LOOK/BUFF commands | `ClientStatement` |
1020
+ | `PlurnkParser.parse` | One operation-bearing model turn; at most one TASK, anywhere | `PlurnkStatement` |
1021
+ | `PlurnkParser.parseStatements` | Zero or more protocol statements | `PlurnkStatement` |
1022
+ | `PlurnkParser.parseLog` | One or more consecutive disposition-ended turns | `PlurnkStatement` |
1023
+ | `PlurnkParser.parseClient` | Executable blocks, including the read-shaped LOOK command | `ClientStatement` |
978
1024
 
979
- Every entry point returns ordered `statement`, `error`, and, where admitted,
980
- `text` items. When present, {§unparsed-tail-boundary} governs the result's item
1025
+ Every entry point ignores outside text under {§whitespace-contract} and returns
1026
+ ordered `statement` and `error` items. When present, {§unparsed-tail-boundary} governs the result's item
981
1027
  extent. The statement `op` field discriminates the generated per-operation
982
1028
  union.
983
1029
 
@@ -999,7 +1045,7 @@ following supported consumer values. All other root exports are TypeScript types
999
1045
  | `InvalidRangeExtentError` | Typed failure from `Validator.assertRangeExtent` | {§range-extent} |
1000
1046
  | `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
1001
1047
  | `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
1002
- | `WORKER_NAME`, `RESERVED_AUTHORITIES` | Authority minting predicate and internal reserved names | {§worker-name} |
1048
+ | `WORKER_NAME` | Authority minting predicate | {§worker-name} |
1003
1049
  | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
1004
1050
 
1005
1051
  §parser-construction-boundary Parser construction components are internal rather
@@ -1242,16 +1288,30 @@ from user-authored prompt content. An adapter may expose no public means to set
1242
1288
  it; Core validates and records it through the same prompt admission path.
1243
1289
 
1244
1290
  §application-worker-observation Worker observation exposes durable identity,
1245
- origin, and immediate parent identity. `readWorker` resolves exactly one id or
1246
- name and returns `null` when absent. `listWorkers` filters collections by origin
1247
- or lineage position; an omitted parent filter means every position and an
1248
- explicit `null` means roots. Singular and plural cardinalities are distinct
1249
- contracts. Observation is not a client binding or permission grant.
1291
+ origin, immediate parent identity, minted `kind` (`conversation`; `fork` for a
1292
+ child carrying a fork boundary; `work` for any other child), and `lifecycle`,
1293
+ the worker's latest work loop projected through {§loop-lifecycle-vocabulary} (`idle`
1294
+ when it has none). Maintenance-only loops do not change this projection;
1295
+ their ordinary turns and results remain durable history. `readWorker` resolves exactly one id or name and returns
1296
+ `null` when absent. `listWorkers` filters collections by origin or lineage
1297
+ position; an omitted parent filter means every position and an explicit `null`
1298
+ means roots. Singular and plural cardinalities are distinct contracts.
1299
+ Observation is not a client binding or permission grant; a client renders kind
1300
+ and lifecycle, it never infers them.
1301
+
1302
+ §loop-lifecycle-vocabulary One projection maps a loop's durable status onto the
1303
+ lifecycle words every client renders, shared by the status gauge and the worker
1304
+ directory: no loop `idle`; 100 `queued`; 102 `running`; 202 `parked`; 200
1305
+ `completed`; any status of 400 or more `failed` (413 budget, 429 turn ceiling,
1306
+ 499 cancel, 500 fail, 504 execution timeout, 508 runaway). `lifecycleOfLoopStatus`
1307
+ in `@plurnk/plurnk-contracts` is that projection's one owner.
1250
1308
 
1251
1309
  §application-loop-observation Loop observation exposes the durable scheduler
1252
- state, exact terminal `OperationResult`, and exact count of packet-bearing
1310
+ state of work loops (excluding maintenance-only administrative loops), exact terminal `OperationResult`, and exact count of packet-bearing
1253
1311
  Turns for one owned Worker. Packetless producer Turns and physical provider
1254
- retries do not contribute to `packetCount`. Exterior
1312
+ retries do not contribute to `packetCount`. Scheduled tasks expose `scheduledAt`
1313
+ (ISO date), optional `intervalMinutes`, and `recurrenceId` (the original task id).
1314
+ Packet notifications carry the same timing; ordinary tasks omit it. Exterior
1255
1315
  adapters consume this projection instead of reconstructing lifecycle from
1256
1316
  events or persistence; events remain the live notification edge.
1257
1317
 
@@ -1270,13 +1330,14 @@ class PlurnkParseError extends Error {
1270
1330
  readonly column: number;
1271
1331
  readonly source: ErrorSource;
1272
1332
  readonly severity: Severity;
1333
+ readonly code?: "invalid-turn-structure";
1273
1334
  }
1274
1335
  ```
1275
1336
 
1276
1337
  §parser-position Parser source locations are points, not text regions. An AST
1277
- statement's `position` identifies the first `#` of its heading; a diagnostic
1278
- identifies the offending or recovery point; a text item and `unparsedTail.from`
1279
- identify the first point at which that item or undefined tail begins. A
1338
+ statement's `position` identifies the first backtick of its header; a diagnostic
1339
+ identifies the offending or recovery point; `unparsedTail.from` identifies where
1340
+ the undefined tail begins. A
1280
1341
  statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
1281
1342
  the unknown sentinel; its dispatch origin remains a separate fact.
1282
1343
 
@@ -1307,46 +1368,86 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
1307
1368
  the sole and complete owner of syntax-error messaging because it holds the
1308
1369
  parse state, lexer mode, and expected-token set that no consumer has. It
1309
1370
  produces the final diagnostic message, deduplicated expected-token lists, and
1310
- turn-shape diagnostics. No valid leading PLAN yields ``No valid leading PLAN
1311
- was parsed; an empty `## PLAN0` was used.``; no valid terminal SEND yields ``No
1312
- valid terminal SEND was parsed; `### SEND0 (NEXT)` was used.``; source with no
1371
+ turn-shape diagnostics ({§turn-shape}). Omitted TASK produces no diagnostic, and
1372
+ neither does the position of a present one ({§disposition-anywhere}). A failed
1373
+ document boundary carries `code: "invalid-turn-structure"`, which cannot be
1374
+ recovered as an individual failed operation. Source with no
1313
1375
  parsed operation yields `no valid Plurnk operation was found.` Targeted
1314
1376
  diagnostics are:
1315
1377
 
1316
- - §matcher-body-redirect **Matcher body in the slot region.** When the
1317
- post-target modifier region begins with `$`, `~`, or `@` with no whitespace
1318
- before it, the lexer redirects the unambiguous matcher to body content below the
1319
- OP heading instead of returning the generic slot list (after whitespace it is
1320
- already the inline body, {§heading-inline-body}). Slash-led regex and XPath are
1321
- excluded because `/` can be target data.
1322
- - §bare-target-redirect **A `(target)` on BARE.** BARE takes no `(path)`; a model that
1323
- writes its prompt, or the prompt's address, into a parenthesized slot (`### BARE0
1324
- (What day is it?)`, `### BARE0 (prompt:///1/1)`) is told that the prompt is the body
1325
- line beneath the heading, with the heading's own opener, instead of the generic
1326
- slot list. Two operator sessions on 2026-08-26 produced exactly these shapes.
1327
- - §combined-anchor-line-redirect **Combined anchor and line number in a scope.**
1328
- A text-coordinate scope containing `@hash:L` or `@hash L` is one bounded hard
1329
- error: `a scope position accepts one line coordinate; use the \`@hash\` anchor
1330
- without its displayed line number`. A malformed header scope is consumed as
1331
- one token at either COPY/MOVE operand; neither produces a punctuation cascade.
1378
+ - §inline-flag-tolerance **PCRE inline modifiers.** A regex whose pattern opens with
1379
+ `(?i)`, `(?m)`, `(?s)` or a combination — the pretrained spelling of a flag, which
1380
+ ECMAScript refuses as an invalid group — is read with those letters lifted into
1381
+ its flags (`/(?i)shutdown|reactor/` is `/shutdown|reactor/i`; `^(?i)note:` is the
1382
+ anchored regex with `i`), with one warning-severity advisory naming the flag
1383
+ position after the closing `/`. Only a leading group of `i`, `m` and `s` lifts;
1384
+ `(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched. From
1385
+ the 2026-09-13 dumbox demo pass, where the model wrote the inline form, was
1386
+ refused, and rewrote it as a trailing flag one turn later.
1387
+ - §regex-trailing-text A valid `/pattern/flags` prefix followed by horizontal
1388
+ whitespace and trailing text receives one concise trailing-content
1389
+ diagnostic, with or without flags, without assuming what the extra text was
1390
+ intended to represent. Invalid patterns or flags retain the native
1391
+ regex failure; no branch silently removes or executes trailing content.
1392
+ - §naked-pattern **The matcher rides the heading bare.** After the path, and any
1393
+ scope or option block, the rest of a FIND, READ or KILL heading line is the
1394
+ matcher, in whichever dialect its first characters claim
1395
+ ({§matcher-prefix-claims}): `/re/i`, `^anchored`, `//xpath`, `$.json`, `~words`,
1396
+ `&symbol`, or a sigil-less glob or literal such as `TODO` or `*.ts`. Those
1397
+ operations take no body, so heading-line text can mean nothing else. On EDIT only
1398
+ a sigil lifts, because plain heading-line text is the replacement body it always
1399
+ was; the lines beneath the heading are then the replacement, and none deletes each
1400
+ match ({§edit-pattern}). A trailing `<!-- aside -->` on the same line stays the
1401
+ aside. The lift is exactly what `[{"pattern": "…"}]` produces, and that option
1402
+ remains the escape for a matcher the heading cannot hold bare: one opening with
1403
+ `(`, `<`, `[` or a backtick, one containing `<!--`, and every COPY/MOVE operand.
1404
+ `FIND (src/parser.ts) /\bparse\w+\b/` is the same operation as
1405
+ `FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
1406
+ dialect without slashes or flags: the whole text is the pattern, so
1407
+ `READ (reasoning:///1/1) ^NOTE:.*` selects a turn's note lines (operator,
1408
+ 2026-09-12: "Recursive Reasoning").
1409
+ - §trailing-slots **Slots after the matcher peel off the right.** The heading text after
1410
+ the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
1411
+ shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
1412
+ trailing `[option block]` that parses as an array of objects come off the right end in
1413
+ any order, each taken once and only when the heading did not already carry that slot,
1414
+ until what remains is the matcher. `READ (a.rs) /fn resolve_/ <1,-1> <!-- entities -->`
1415
+ is the same operation as `READ (a.rs) <1,-1> /fn resolve_/ <!-- entities -->`, with
1416
+ one warning-severity advisory naming the canonical order for the scope or block
1417
+ (operator, 2026-09-13: "swallow up anything that passes as legitimate plurnk"; the
1418
+ 2026-09-13 dumbox run refused three headings for this in one turn). A matcher that
1419
+ itself ends in one of those shapes takes the option escape.
1420
+ - §matcher-body-redirect **A body beneath those headings.** Text below the heading
1421
+ of a FIND, READ or KILL is a body, and those operations take none: the builder
1422
+ keeps the statement without it and raises one warning-severity advisory (`READ
1423
+ takes no body; the body was ignored. A pattern belongs on the opening fence line
1424
+ after the path.`), delivered like {§misplaced-aside-advisory} as a
1425
+ `parse_advisory` notice (operator, 2026-09-12: a gentle warning, never an error
1426
+ the model must recover from). One sigil line beneath the heading is the bare form
1427
+ written a line low and still lifts; nothing else is promoted into a matcher from
1428
+ below the heading, and the advisory never echoes the body.
1429
+ - §combined-anchor-tolerance **Combined anchor and line number in a scope.** A
1430
+ text-coordinate scope position written `@hash:L` or `@hash L` is the displayed
1431
+ `@abcde 42:` prefix copied whole (a koota-entity turn refused nine of them in a
1432
+ row, 2026-09-12): the position is the anchor, the number is dropped, and one
1433
+ warning-severity advisory names the anchor-only form. The scope lexes as one
1434
+ ordinary marker at any text-coordinate operation, either COPY/MOVE operand
1435
+ included; nothing cascades.
1332
1436
  - §invalid-scope-diagnostic **Malformed scope content.** After a properly spaced
1333
1437
  scope opener, report the offending scope (at most 64 code points, ending at
1334
1438
  `>` or the heading's line end) and its operation's constraint: FIND result
1335
- positions, EXEC/WAIT minutes, text coordinates, or no scope. Do not append advice for
1439
+ positions, execution/TASK minutes, text coordinates, or no scope. Do not append advice for
1336
1440
  other operations or infer why the producer supplied the value. Spacing and
1337
1441
  boundary-loss diagnostics retain their own contracts.
1338
- - §label-recipient-redirect **A label beside a recipient.** `### SEND0 (TERM)
1339
- (worker://parent)` and `### SEND0 (worker://parent) (TERM)` are one parser error at
1340
- the heading: `a (NEXT|WAIT|TERM|FAIL) SEND names no recipient; message a recipient
1341
- with its own SEND first` ({§send-label}).
1342
- - §misplaced-annotation-advisory **Annotation in the body.** A READ or FIND whose
1442
+ - §misplaced-aside-advisory **Aside in the body.** A READ or FIND whose
1343
1443
  body is solely an HTML comment (`<!-- … -->`) can never carry a matcher: it is
1344
- the annotation the model put on the line below the heading. The builder takes
1345
- the comment as the annotation when the heading has none, builds the operation
1444
+ the aside the model put on the line below the heading. The builder takes
1445
+ the comment as the aside when the heading has none, builds the operation
1346
1446
  with no body, and raises one warning-severity advisory stating that observed
1347
1447
  normalization; the parser places the advisory right after its statement and
1348
1448
  the service delivers it as a `parse_advisory` notice with its position. A body
1349
- with any other content is a matcher, as before.
1449
+ with any other content is ignored under the same advisory path
1450
+ ({§matcher-body-redirect}).
1350
1451
 
1351
1452
  §error-shape The diagnostic class determines how much guidance the parser may
1352
1453
  provide:
@@ -1358,18 +1459,20 @@ provide:
1358
1459
  | Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
1359
1460
  | Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
1360
1461
 
1361
- All messages use PLURNK protocol vocabulary: heading, lane, signal, target,
1462
+ All messages use PLURNK protocol vocabulary: opening fence, closing fence, target,
1362
1463
  scope, line marker, body, section boundary, or space between slots. They never
1363
1464
  expose ANTLR rule or token names. They refer to a slot or
1364
1465
  feature rather than an implementation rule. Generic tutoring, speculative
1365
1466
  intent, coordinate restatement, and multiple repair strategies are forbidden.
1467
+ Unexpected top-level text immediately after a closed operation identifies that
1468
+ operation's opening line, closing line, and matching backtick count.
1366
1469
 
1367
1470
  Examples of canonical hard facts:
1368
1471
 
1369
1472
  - `unrecognized character '<' in target`
1370
- - `unrecognized character ':' in signal`
1473
+ - `unexpected bracket modifier; the fence name selects the executor`
1371
1474
  - `unrecognized character 'X' in statement header`
1372
- - `a turn must begin with \`## PLAN0\``
1475
+ - `TASK's body begins below the header`
1373
1476
  - `expected ')'; got ':'`
1374
1477
 
1375
1478
  Each malformed statement produces at most one hard error. The first recorded
@@ -1380,8 +1483,9 @@ Independent malformed statements each retain one hard error. Advisories remain
1380
1483
  separate because they do not represent failed admission.
1381
1484
 
1382
1485
  §unparsed-tail-boundary When the lexer cannot determine where a malformed
1383
- statement ends, the result's `unparsedTail` marks the position from which
1384
- parsing gave up. `ParseResult.items` contains only facts that begin strictly
1486
+ statement ends — an unfinished `(target` or `[metadata` slot on a heading line —
1487
+ the result's `unparsedTail` marks the position from which parsing gave up. A block
1488
+ without a closer is not such a case: it ends under {§closer-fallback}. `ParseResult.items` contains only facts that begin strictly
1385
1489
  before that point; recovered contexts and diagnostics at or beyond it are not
1386
1490
  public results. The tail is one separate boundary fact, not an additional
1387
1491
  malformed-statement diagnostic. Consumers must treat anything from that point
@@ -1404,6 +1508,6 @@ runtime constructs this; the parser provides the fields):
1404
1508
  "column": 12,
1405
1509
  "source": "parser",
1406
1510
  "severity": "error",
1407
- "message": "target slot of `### READ0` opened at line 1 but never closed - add `)`"
1511
+ "message": "READ block opened at line 1 but was not closed with 3 backticks"
1408
1512
  }
1409
1513
  ```