@plurnk/plurnk-contracts 1.16.4 → 1.17.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 (88) hide show
  1. package/README.md +13 -29
  2. package/SPEC.md +605 -518
  3. package/dist/conformance/agui-v1.json +3 -3
  4. package/dist/schema/CapabilityProjection.json +2 -4
  5. package/dist/schema/ClientStatement.json +5 -52
  6. package/dist/schema/FunctionalityDefinitionState.json +2 -2
  7. package/dist/schema/LineMarker.json +1 -1
  8. package/dist/schema/LoopPolicy.json +2 -3
  9. package/dist/schema/MatcherBody.json +6 -6
  10. package/dist/schema/McpServerDefinition.json +17 -0
  11. package/dist/schema/ModelCatalogPage.json +10 -1
  12. package/dist/schema/ModelRoute.json +9 -0
  13. package/dist/schema/Notice.json +1 -1
  14. package/dist/schema/ParsedPath.json +3 -2
  15. package/dist/schema/Plan.json +9 -5
  16. package/dist/schema/PlurnkStatement.json +86 -134
  17. package/dist/schema/ProposalProjection.json +2 -1
  18. package/dist/schema/ResourceSelection.json +51 -27
  19. package/dist/schema/SkillDefinition.json +3 -3
  20. package/dist/src/AcpPlanValue.d.ts +0 -1
  21. package/dist/src/AcpPlanValue.d.ts.map +1 -1
  22. package/dist/src/AcpPlanValue.js +14 -13
  23. package/dist/src/AcpPlanValue.js.map +1 -1
  24. package/dist/src/ApplicationPort.d.ts +16 -14
  25. package/dist/src/ApplicationPort.d.ts.map +1 -1
  26. package/dist/src/AstBuilder.d.ts +3 -2
  27. package/dist/src/AstBuilder.d.ts.map +1 -1
  28. package/dist/src/AstBuilder.js +340 -187
  29. package/dist/src/AstBuilder.js.map +1 -1
  30. package/dist/src/JsonDocument.d.ts +2 -0
  31. package/dist/src/JsonDocument.d.ts.map +1 -0
  32. package/dist/src/JsonDocument.js +14 -0
  33. package/dist/src/JsonDocument.js.map +1 -0
  34. package/dist/src/LoopLifecycle.d.ts +3 -0
  35. package/dist/src/LoopLifecycle.d.ts.map +1 -0
  36. package/dist/src/LoopLifecycle.js +14 -0
  37. package/dist/src/LoopLifecycle.js.map +1 -0
  38. package/dist/src/PlanValue.d.ts +1 -1
  39. package/dist/src/PlanValue.d.ts.map +1 -1
  40. package/dist/src/PlanValue.js +10 -6
  41. package/dist/src/PlanValue.js.map +1 -1
  42. package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -1
  43. package/dist/src/PlurnkErrorStrategy.js +58 -139
  44. package/dist/src/PlurnkErrorStrategy.js.map +1 -1
  45. package/dist/src/PlurnkParseError.d.ts +3 -1
  46. package/dist/src/PlurnkParseError.d.ts.map +1 -1
  47. package/dist/src/PlurnkParseError.js +4 -1
  48. package/dist/src/PlurnkParseError.js.map +1 -1
  49. package/dist/src/PlurnkParser.d.ts +9 -5
  50. package/dist/src/PlurnkParser.d.ts.map +1 -1
  51. package/dist/src/PlurnkParser.js +175 -180
  52. package/dist/src/PlurnkParser.js.map +1 -1
  53. package/dist/src/RecordingListener.d.ts +1 -1
  54. package/dist/src/RecordingListener.d.ts.map +1 -1
  55. package/dist/src/RecordingListener.js +24 -6
  56. package/dist/src/RecordingListener.js.map +1 -1
  57. package/dist/src/TurnDisposition.d.ts +11 -0
  58. package/dist/src/TurnDisposition.d.ts.map +1 -0
  59. package/dist/src/TurnDisposition.js +32 -0
  60. package/dist/src/TurnDisposition.js.map +1 -0
  61. package/dist/src/Validator.js +1 -1
  62. package/dist/src/Validator.js.map +1 -1
  63. package/dist/src/generated/plurnkLexer.d.ts +95 -106
  64. package/dist/src/generated/plurnkLexer.d.ts.map +1 -1
  65. package/dist/src/generated/plurnkLexer.js +674 -828
  66. package/dist/src/generated/plurnkLexer.js.map +1 -1
  67. package/dist/src/generated/plurnkParser.d.ts +111 -152
  68. package/dist/src/generated/plurnkParser.d.ts.map +1 -1
  69. package/dist/src/generated/plurnkParser.js +756 -1082
  70. package/dist/src/generated/plurnkParser.js.map +1 -1
  71. package/dist/src/generated/plurnkParserVisitor.d.ts +15 -36
  72. package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -1
  73. package/dist/src/generated/plurnkParserVisitor.js +12 -30
  74. package/dist/src/generated/plurnkParserVisitor.js.map +1 -1
  75. package/dist/src/index.d.ts +5 -1
  76. package/dist/src/index.d.ts.map +1 -1
  77. package/dist/src/index.js +4 -1
  78. package/dist/src/index.js.map +1 -1
  79. package/dist/src/types.d.ts +3 -7
  80. package/dist/src/types.d.ts.map +1 -1
  81. package/dist/src/types.generated.d.ts +147 -80
  82. package/dist/src/types.generated.d.ts.map +1 -1
  83. package/dist/src/types.js +3 -3
  84. package/dist/src/types.js.map +1 -1
  85. package/package.json +4 -8
  86. package/plurnk.md +103 -119
  87. package/dist/plurnk.gemma.gbnf +0 -141
  88. package/dist/plurnk.qwen.gbnf +0 -130
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,224 @@ 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
316
-
317
- ## OPdelimiter (path)? {metadata}* <scope>? <!-- annotation -->?
318
- body?
319
256
  ```
320
257
 
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 |
258
+ ```OP (path)? <scope>?```
259
+
260
+ ```executor (program-or-tool)?
261
+ input
262
+ ```
263
+ `````
264
+
265
+ §section-boundary Every statement is one backtick block. Its header occupies one
266
+ physical line: a fence of at least three backticks, an optional numeric delimiter
267
+ ({§numeric-delimiter}), then the name and its slots. A closer is shown by
268
+ convention and never demanded ({§fence-closer}, {§closer-fallback}). There are no
269
+ operation suffixes or heading levels. Nothing in the language is counted by the
270
+ author: every boundary is an anchored line the parser recognizes by its first
271
+ characters (operator, 2026-09-12: counted pairs failed 52 of 363 turns on
272
+ GLM-5.3-flash; anchored tokens failed none).
273
+
274
+ §fence-closer A block opened with N backticks and delimiter D (its digits, possibly
275
+ none) closes at the first line at column zero made of at least N backticks,
276
+ exactly D, and nothing else but horizontal whitespace. Count follows CommonMark: a
277
+ shorter fence inside the body is body; an equal or longer bare fence closes a bare
278
+ block. The delimiter compares exactly: a bare fence never closes a delimited block,
279
+ and a delimited fence never closes a bare one. The compact one-line form closes on
280
+ its heading line after the modifiers under the same rule.
281
+
282
+ §numeric-delimiter Digits between the opening backticks and the name (an opener
283
+ carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it. This is how a
284
+ block nests fences of its own width: with a delimiter, a body may carry bare fences
285
+ and headings of the same count. The delimiter is syntax, never AST or persistence
286
+ state; `PlurnkParser.frame` chooses one when the body it wraps holds a heading line
287
+ of four or more backticks ({§statement-rendering}).
288
+
289
+ §fence-heading-in-body A fence line of four or more backticks, optional digits, and
290
+ a name that is a native operation or a known executor is a heading wherever it
291
+ stands. Inside an open block it ends that block without closing it
292
+ ({§closer-fallback}) and opens the next statement. Fence lines of fewer than four
293
+ backticks are headings only outside any block. Known executors are `sh` plus what
294
+ the host names in `ParseOptions.executors`. Consequences: a closer glued to the next
295
+ opener (eight backticks then `READ`) can never swallow the rest of a turn, and a quoted
296
+ heading of four or more backticks inside a body needs the numeric delimiter to stay body.
297
+
298
+ §closer-fallback A block that ends at a heading or at the end of the input has no
299
+ closer of its own. Its body is cut back to its last bare fence line (any count,
300
+ optional digits), which is the closer the author meant, and one terminating line
301
+ ending goes with it; when no bare fence line exists the body is the whole span less
302
+ one terminating line ending. This carries no diagnostic: a missing closer is never
303
+ an admission failure, and {§unparsed-tail-boundary} is not involved.
304
+
305
+ §fence-boundary Inside a body, fences are read by count and delimiter, never by
306
+ name, except for the heading rule above:
307
+
308
+ | Fence encountered inside a body | Meaning |
309
+ |---|---|
310
+ | Fewer backticks than the block's own | Body |
311
+ | At least the block's backticks, bare, block undelimited | The block's closer |
312
+ | At least the block's backticks carrying the block's delimiter | The block's closer |
313
+ | At least the block's backticks with any other delimiter | Body |
314
+ | Four or more backticks naming a native operation or known executor | A heading: ends the block, opens the next statement |
315
+
316
+ §indented-fences Leading horizontal whitespace before an opener or a closer is
317
+ not part of the fence: an indented fence line is a fence line, on openers,
318
+ closers, headings that end a block, and the closer fallback. A body keeps its own
319
+ lines' indentation. CommonMark allows three spaces; this allows any, because a
320
+ model that indents an emission indents all of it (operator, 2026-09-12: measured
321
+ at five to ten percent of emissions on GLM-5.3-flash).
322
+
323
+ §inline-chain A closer on a heading line, or on a body's closing line, may be
324
+ followed on that same line by the next opener; the closer still closes, and the
325
+ opener opens. This absorbs the habit of writing several operations in one
326
+ paragraph after prose. Prose after a closer on its line ends the chain.
327
+
328
+ §executor-case **An executor tag in any case.** A fence tag that matches a
329
+ registered executor's name case-insensitively opens that executor (`SH` opens
330
+ `sh`), and the statement's `executor` is the registered spelling, so a lookup
331
+ by that name never misses. Operation names stay uppercase by teaching and were
332
+ never written otherwise in 12,000 fences; one `SH` in 1,300 `sh` fences was
333
+ (2026-09-13 census). An unregistered name in any case is still prose
334
+ ({§interstitial-fence}).
335
+
336
+ §one-line-turn **A whole turn on one line.** The most frequent private rejection
337
+ across the 2026-09-12/13 dumbox runs (five of eleven) was a turn emitted as a
338
+ single line: prose, then heading after heading with no line ending anywhere. Two
339
+ rules absorb it. The next opener on a heading's own line, after the heading's
340
+ slots, ends that heading's block bodyless and opens ({§empty-section}), so
341
+ `````EDIT (a.rs) <60,66> <!-- drop --> ````EDIT (b.rs) <31,38>` is two scoped
342
+ deletions; and a TASK whose inventory rides its heading line as a `[…]` block
343
+ (`````TASK [{"content": "…", "status": "in_progress"}] ````) takes that block as
344
+ its body when nothing sits beneath the heading, with one warning-severity
345
+ advisory naming the body as where the inventory belongs. A block beneath the
346
+ heading still wins.
347
+
348
+ §anchor-digits In a text scope, `@` followed by one to four digits cannot be a
349
+ hash and is read as that line number, with one warning-severity advisory naming
350
+ the five-character anchor form. Five characters after `@` are always an anchor.
351
+
352
+ §unclosed-aside A heading whose aside opens with `<!--` and never closes on its
353
+ line takes the rest of the line as the aside, with one warning-severity advisory.
354
+ A closed aside followed by more text is unchanged.
355
+
356
+ §interstitial-fence A fence line that names no native operation and no known
357
+ executor opens nothing: unlabeled, or tagged like a code block (`ts`, `json`),
358
+ outside a block it is prose and ignored like every other outside line
359
+ ({§whitespace-contract}); inside a body it is body. Nothing is promoted into a
360
+ header or recursively parsed. There is no implicit SEND: a reply is an explicit
361
+ `SEND` block. (This replaces the retired unlabeled-fence SEND of the fences
362
+ chapter, whose unlabeled fences turned displaced headings into silent messages.)
363
+
364
+ §bare-heading-advisory An operation name that opens a line outside any block in the
365
+ shape of a heading (`READ (…)`, `TASK`, …) is prose and runs nothing. The parser
366
+ emits one warning-severity advisory naming the fence form, placed after the parsed
367
+ items, so the loss is never quiet.
368
+
369
+ §empty-section Both the compact bodyless form and an empty multiline block
370
+ normalize optional bodies to null. TASK normalizes an empty body to `[]`
371
+ under {§plan-value}. Closing fences are conventional, never required
372
+ ({§closer-fallback}).
373
+
374
+ §statement-rendering `PlurnkParser.stringify` renders native OP names and named
375
+ EXEC executors from the shared AST, with one blank line between operations.
376
+ Every closing fence occupies its own line, including bodyless operations;
377
+ inline fences remain accepted input, not generated examples.
378
+ It chooses at least four backticks and more than any run within the body, and a
379
+ numeric delimiter whenever the body holds a heading line of four or more backticks
380
+ ({§fence-heading-in-body}), preserving body bytes on reparse. Fence length and
381
+ delimiter are syntax, not AST or persistence state. Core-authored programs use
382
+ this serializer and the ordinary admission parser.
383
+
384
+ | Element | Contract |
385
+ |---|---|
386
+ | Fence name | Reserved native OP, otherwise a registered executor or attached MCP service |
387
+ | `(path)` | Target/program/tool slot; COPY and MOVE each have two resource operands |
388
+ | `[metadata]` | One JSON array of option objects, owner-interpreted; options, never the op's input |
389
+ | `<scope>` | Operation-specific numeric or anchored coordinates |
390
+ | `<!-- … -->` | Optional final, single-line aside |
391
+ | Body | Literal content between framing newlines |
392
+ | Closing fence | The opening backtick count and delimiter, on its own line |
393
+
394
+ §slot-order Producers put target, scope, metadata, then aside, separated
395
+ by one ASCII space. Target and scope form one resource selection; COPY/MOVE
396
+ repeat the complete selection/metadata group per operand. ANTLR accepts
397
+ adjacent slots and scope/metadata permutations within a selection without
398
+ changing ownership or making them distinct canonical forms. Each selection
399
+ has at most one scope; its metadata blocks retain their authored order.
400
+
401
+ §plan-slotless TASK accepts no target or metadata. Its optional scope carries
402
+ waiting timing; its inventory body begins below the header.
403
+
404
+ §heading-inline-body Nonempty body text belongs below the fence header.
405
+ The ingester tolerates body text after horizontal whitespace on the header,
406
+ preserves it, and emits one warning stating that normalization. This does not
407
+ change the meaning of a compact empty block or permit unmatched fences.
408
+
409
+ §operation-aside The final header modifier may be one single-line HTML
410
+ comment. AstBuilder removes its delimiters and surrounding whitespace into
411
+ `aside: string | null`. It is durable descriptive text, not authority,
412
+ routing, timing, or body input. Comments inside a body remain literal except
413
+ for the narrowly owned {§misplaced-aside-advisory}.
414
+
415
+ §scheme-metadata-modifier A target may carry one single-line `[metadata]`
416
+ block after its scope; an executor fence also admits it without a target.
417
+ Read with its brackets, the block is a JSON array of option objects, merged
418
+ left to right with later keys winning; the keys belong to the selected scheme
419
+ or executor, which owns interpretation, validation and authority. The language
420
+ assigns no meaning to the content and stores each block's exact inner text:
421
+ balanced brackets inside the block are retained, and double-quoted strings
422
+ protect their brackets. Brackets inside `(path)` remain ordinary path and
423
+ glob characters. A block that is not valid JSON, or a second block on one
424
+ operand, is the owner's `400`, never a parser diagnostic. An unfinished block
425
+ or multiline metadata loses its boundary. One key is the language's own:
426
+ `pattern` ({§matcher-option}).
427
+
428
+ §matcher-option **`pattern` is the matcher, and it lives in the heading.** On
429
+ FIND, READ, KILL, EDIT, and each COPY/MOVE operand, the option
430
+ `[{"pattern": "<matcher>"}]` carries the matcher string exactly as a body once
431
+ did: the leading prefix claims its dialect under {§matcher-prefix-claims}, and
432
+ AstBuilder lifts it into the statement's `matcher` (`MatcherBody | null`),
433
+ positioned dialect errors included. A block that carries only `pattern`
434
+ leaves `metadata: null` for the owner; beside other keys the block stays with
435
+ the owner verbatim, and the owner's reader treats `pattern` as reserved. The
436
+ language lifts only from one block that parses as a JSON array of objects;
437
+ anything else lifts nothing and reaches the owner's `400` untouched. A
438
+ `pattern` that is present but not a string is the language's own positioned
439
+ diagnostic, as is a matcher of a claimed dialect that fails admission. FIND,
440
+ READ, and KILL take no body at all: a body beneath their heading is ignored and
441
+ the operation still runs, with one warning-severity advisory naming the
442
+ heading-line form (`FIND takes no body; the body was ignored. A pattern belongs
443
+ on the opening fence line after the path.`); it is never silently read as a
444
+ matcher, and it never strikes ({§matcher-body-redirect}). The heading line itself
445
+ is the matcher's home ({§naked-pattern}). A body that is only an HTML comment is
446
+ still the aside under {§misplaced-aside-advisory}. EDIT keeps its literal body:
447
+ with a matcher it is the replacement for every selected span ({§edit-pattern}),
448
+ and an absent body deletes them. `PlurnkParser.stringify` writes a lifted matcher
449
+ whose block left no metadata back bare when the bare form reads back identically
450
+ ({§naked-pattern}), otherwise as `[{"pattern": "…"}]`.
409
451
 
410
452
  ## 3. Lexical elements
411
453
 
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 |
454
+ | Element | Shape or role |
455
+ |---|---|
456
+ | Native OP | `FIND READ EDIT COPY MOVE SEND EXEC BARE WORK FORK KILL TASK` |
457
+ | Executor name | Letters, digits, `_`, `.`, `+`, or `-`; reserved OPs win |
458
+ | Fence | Three or more backticks, matched by exact count |
459
+ | `(path)` | Local path, URI, program or tool name; §5 |
460
+ | `[metadata]` | One JSON array of owner-defined option objects |
461
+ | `<scope>` | Numeric or anchored coordinates; §7 |
462
+ | Body | Literal text; never recursively interpreted as operations |
421
463
 
422
464
  ## §op-shapes 4. Per-operation semantics
423
465
 
@@ -426,83 +468,114 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
426
468
 
427
469
  | OP | `(path)` | `<scope>` | `body` |
428
470
  |------|----------------------------------------------|---------------------------------|--------------------------------|
429
- | PLAN | none | none | required Plurnk Plan JSON array |
430
471
  | FIND | required target or glob | optional result range | optional matcher |
431
472
  | READ | required target | optional text region | empty |
432
473
  | EDIT | required file or entry | required for an existing target | literal text |
433
474
  | COPY | required source and destination | optional region after each path | empty |
434
475
  | 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.
476
+ | EXEC | fence names executor; optional program/tool path ({§exec-executor-slot}) | optional timeout, poll | optional program input |
477
+ | BARE | optional prompt resource | none | prompt; optional with a path |
478
+ | WORK | optional fresh `worker://name`, or a prompt resource ({§worker-spawn-prompt-resource}) | none | prompt; optional with a resource |
479
+ | FORK | optional context-inheriting `worker://name`, or a prompt resource | none | prompt; optional with a resource |
480
+ | KILL | required target, including a log item | optional text region ({§kill-scope}) | none; the matcher is the `pattern` option |
481
+ | SEND | optional recipient | optional recipient timing | message |
482
+ | TASK | none | optional timeout and poll for waiting intent | Plurnk Plan JSON array |
483
+
484
+ §operation-code-polymorphism Operation-result statuses and turn dispositions are
485
+ distinct facts. TASK derives lifecycle intent from its inventory;
486
+ SEND and KILL carry no disposition operand.
487
+
488
+ §plan-value **TASK carries the complete current task inventory.** Admission
489
+ parses one JSON array in any whitespace layout, including
490
+ {§json-result-rendering}, strips unknown entry keys, and validates string
491
+ `content` and native `status`. Opaque `_meta` remains optional. Nonempty plain
492
+ text, malformed JSON, or an invalid array becomes one `in_progress` entry
493
+ containing the exact body, with one factual warning. No partial repair or list
494
+ inference occurs. A blank body becomes `[]`, never inferred completion.
495
+ The normalized array is the sole semantic value in AST, persistence and model
496
+ log; exact authored bytes remain in `turnOps`. Earlier inventories are history,
497
+ not accumulated obligations. Task descriptions are not executable dependencies.
498
+
499
+ §task-inventory-intent The first matching row determines intent, independently
500
+ of entry order. Actual execution adjudicates intent under {§wait-obligation-matrix}.
501
+
502
+ | Inventory condition | Intent | Derived lifecycle status |
503
+ |---|---|---|
504
+ | TASK omitted | Continue silently | 102 |
505
+ | Explicit empty inventory | Recover empty inventory | 102 |
506
+ | Any `in_progress` | Continue independent actionable work | 102 |
507
+ | Any `waiting`, no `in_progress` | Await work or an event | 202 |
508
+ | Any `pending`, no actionable or waiting entry | Review blocked dependencies | 102 |
509
+ | All terminal, any `completed` | End successfully | 200 |
510
+ | All `failed`, nonempty | End unsuccessfully | 499 |
511
+
512
+ `pending` is blocked on another task; `in_progress` can be actively advanced;
513
+ `waiting` awaits an ongoing stream, worker or external event. `completed` is
514
+ successful resolution; `failed` is unsuccessful resolution. A failed sibling
515
+ does not terminate independent unfinished work. The engine does not infer a
516
+ dependency graph from task text.
466
517
 
467
518
  §plan-acp-projection **Only an ACP-facing boundary projects the model-native
468
519
  Plan.** It constructs ACP's `{ "entries": [...] }` Plan object from the internal
469
520
  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.
521
+ (the model-native Plan carries none). The internal value is never mutated.
522
+ Native `waiting` maps to ACP `in_progress`, with `Waiting:` and a space prepended
523
+ to its display content; native `failed` maps to ACP `completed`, with `Failed:` and a space.
524
+ Both carry their native status in `_meta["plurnk.xyz/status"]`, an edge-owned
525
+ key derived from the native status rather than trusted from authored metadata.
526
+ Other statuses and unrelated metadata remain unchanged. The labels preserve
527
+ meaning even when a generic client ignores extension metadata.
474
528
  The projected value validates against the separately owned ACP Plan schema pinned
475
529
  to ACP v1
476
530
  [`schema-v1.21.0`](https://github.com/agentclientprotocol/agent-client-protocol/tree/schema-v1.21.0)
477
531
  commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
478
532
 
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.
533
+ §exec-executor-slot The fence name selects the executor directly: for example,
534
+ `python3 (tools/report.py)` or `gitea (issue_list)` on the opening fence line.
535
+ Reserved native OP names take precedence. Other names lower to the same EXEC
536
+ AST with `executor`, `target`, metadata, timing and body fields.
537
+ Registration is checked by the runtime, not by the syntax parser. An attached
538
+ MCP service uses that executor path and its owner validates the named tool and
539
+ input-body JSON against its schema. Unknown names do not fall back to a shell.
540
+ The native `EXEC` form without a selected executor retains the runtime's
541
+ default executor contract; canonical shell examples name `sh` explicitly.
542
+ The path names a program or tool and is never split. Metadata such as
543
+ `[{"cwd": "…"}]` remains interpreted by the selected executor.
544
+
545
+ §turn-disposition TASK is the sole workflow declaration. `TurnDisposition`
546
+ derives intent from its canonical inventory under {§task-inventory-intent}.
547
+ The AST has no independently settable lifecycle status, target or metadata.
548
+ SEND deliberately messages its recipient, or the user when targetless; it
549
+ neither changes task status nor terminates a run. A program contains one final
550
+ TASK, not last-wins competing inventories. Former lifecycle names are not aliases.
551
+
552
+ §send-wait-scope TASK accepts `<timeout[,poll]>` in whole minutes. It applies
553
+ only to a waiting intent ({§park-202-only}); the dispatcher validates its bounds.
554
+ Otherwise it is unused, with a factual warning rather than a changed outcome.
555
+
556
+ §send-directed-scope A recipient SEND preserves an optional numeric scope after
557
+ its target and metadata. The addressed owner assigns its semantics; worker
558
+ actors use `<delay[,interval]>` ({§worker-scheduled-send}). A targetless message
559
+ takes no scope. Scheduling does not change the message body or disposition.
493
560
 
494
561
  §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 }`.
562
+ anchored (```` ```KILL (log:///**/READ) <17,-1>``` ```` or
563
+ ```` ```KILL (worker:///notes.md) <@aB3dE,@0Aa9Z>``` ````), and an optional matcher option that
564
+ selects rows or lines (```` ```KILL (log:///**) [{"pattern": "~stale"}]``` ````, {§matcher-option}).
565
+ The AST is `{ op: "KILL", target, lineMarker: TextLineMarker | null, matcher: MatcherBody | null, body: null }`.
498
566
  Without a scope, KILL retires or deletes the whole target; with one, it removes exactly
499
567
  that span — of a log body's packet projection or of an entry's content. Core owns the
500
568
  one-way semantics: there is no operation that restores a scoped-away log body.
501
569
 
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.
570
+ §legacy-bracket-slot Brackets are the metadata modifier, never an executor
571
+ selector: the runtime or MCP service is the fence name, and tool input belongs
572
+ in the body. A bracket block that leads an executor fence or follows a target
573
+ is metadata, so a legacy `[node]` selector reaches its owner as metadata text
574
+ and is refused there; a bracket before the target of a non-executor OP is one
575
+ bounded header diagnostic that selects nothing.
503
576
 
504
577
  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,
578
+ scopes ordered results. EXEC and SEND scope owner-defined timing. READ, EDIT, COPY,
506
579
  MOVE, and KILL use one universal text algebra independent of mimetype; a log
507
580
  KILL admits only its one- and two-line forms for canonical log-body visibility:
508
581
 
@@ -525,22 +598,37 @@ four-coordinate region ending after the final code point of `endLine`.
525
598
  Producers never emit that form. Other arities and decimal text coordinates are
526
599
  runtime 416 failures.
527
600
 
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
601
+ §bare-statement **BARE requests one isolated model inference.** Its optional
602
+ path names a prompt resource; its body supplies inline prompt text. At least
603
+ one must supply nonempty text at execution. With both, the complete resource
604
+ text precedes the body, separated by two newlines. The target's scheme owns any
605
+ metadata modifier. No scope, persistent worker identity, or output-language
606
+ shape is represented. Provider selection, source admission, batching,
607
+ accounting, and observation timing belong to the consuming service.
608
+
609
+ §read-find-normalization An authored READ is never rewritten into a FIND. A
610
+ glob target on READ keeps its glob, and the runtime fans it out into one exact
611
+ READ per matching path, with the authored scope and matcher ({§read-fan-out}
612
+ in the core SPEC; operator, 2026-09-13: "give it what it asked for" — a model
613
+ that asks to read every file under a glob gets those files, bounded by the
614
+ FIND page and the preview scope, not a catalog it did not ask for). A matcher
615
+ never changes the operation either: READ with a `pattern` on an exact target
616
+ stays READ and renders the selected lines ({§read-pattern}). The survey of
617
+ paths is FIND, and only FIND.
618
+
619
+ §local-path-fragment **A bare path takes `#channel` like a URL.** `data/users.html#readable`
620
+ parses as `{ kind: "local", raw: "data/users.html", fragment: "readable" }`: the first `#` ends
621
+ the path and the rest is the channel (a spelling that opens with `#` names no path and stays
622
+ whole), exactly as `worker:///a.html#readable` decomposes, so
623
+ the `#channel` a READ receipt advertises (`channels: {"#readable": N}`) is addressable in the
624
+ same bare spelling the receipt used (2026-09-13 dumbox demo: the model appended it and was
625
+ told no entry existed at `users.html#readable`). `raw` is therefore always the path alone; a
626
+ bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
627
+ Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
628
+
629
+ §read-exact-target READ targets one exact resource (a local path or scheme
630
+ URL, with optional `#channel` fragment or `[metadata]`) and has no body. A
631
+ `<scope>` on READ selects
544
632
  a text region from that exact target. Without a scope, READ defaults to
545
633
  `<1,16>`; `<1,-1>` explicitly selects all text. Decimal scope components are
546
634
  invalid on READ.
@@ -554,7 +642,7 @@ Mutation semantics:
554
642
  - `<0>` prepends and `<-1>` appends.
555
643
  - §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
644
  - `<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.
645
+ - §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)`, optional `<scope>`, and optional `{metadata}` blocks; modifiers bind only to that operand. The two operands independently select their resource, channel, scheme metadata, and text region.
558
646
 
559
647
  ### §operation-observation Per-operation observations
560
648
 
@@ -571,7 +659,7 @@ Mutation semantics:
571
659
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
572
660
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
573
661
  | KILL | Status of deletion or termination |
574
- | PLAN | Status of durable complete-Plan logging |
662
+ | TASK | Current inventory and adjudicated lifecycle outcome |
575
663
 
576
664
  §find-result-unit For FIND, authored target shape fixes the paginated result
577
665
  unit. An exact target with a matcher pages flat match locations; a glob or
@@ -588,8 +676,8 @@ EDIT. Whole-channel transfers remain bodyless structural effects; runtime
588
676
  owners reject binary markers rather than treating a text field as a byte lane.
589
677
 
590
678
  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;
679
+ {§operation-result}. Its `status` belongs to the result envelope; TASK supplies
680
+ the authored lifecycle intent. Durable operation observations are projected into a later packet;
593
681
  retrieval never returns inline within the emitting turn.
594
682
 
595
683
  ## §path-syntax 5. Target and path grammar
@@ -599,7 +687,7 @@ and path globs share the slot; content matchers belong in the body.
599
687
 
600
688
  | Form | Typed admission | Runtime meaning |
601
689
  |-------------------------|---------------------------------------------------------------------|------------------------------------------------------|
602
- | Bare path | `LocalPath { kind: "local", raw }` | Resolves through the runtime's file surface |
690
+ | Bare path | `LocalPath { kind: "local", raw, fragment? }` | Resolves through the runtime's file surface; `#channel` is `fragment` ({§local-path-fragment}) |
603
691
  | `scheme://…` | WHATWG-decomposed `UrlPath` | Resolves only when a runtime scheme owns the address |
604
692
  | Path glob | Preserved in either path kind | Scheme defines collection selection and ordering |
605
693
  | `#channel` fragment | Preserved as `UrlPath.fragment` | Selects a named channel when the scheme supports it |
@@ -655,60 +743,60 @@ ingestion restriction: the parser decomposes arbitrary URL authorities.
655
743
 
656
744
  ## §matcher-prefix-claims 6. Bulk pattern matching
657
745
 
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.
746
+ FIND, READ, KILL, EDIT, and the COPY/MOVE operands accept an optional matcher
747
+ through the `pattern` option ({§matcher-option}); the client-tier LOOK still
748
+ carries its matcher as a body. AstBuilder assigns the dialect from the
749
+ matcher's leading characters. A leading prefix claims its dialect. Invalid
750
+ claimed syntax is a positioned visitor error and never falls back to glob
751
+ matching.
664
752
 
665
753
  - §heading-boundary-recovery A column-0 heading is the trustworthy boundary. After a
666
754
  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
755
+ next heading; the turn shape is decided locally (a turn disposition is recognized by its own
756
+ token, never by a whole-turn alternative), so one malformed heading costs one
757
+ diagnostic and every later statement, the turn disposition included, stands on its own. Any
670
758
  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>)`)
759
+ - §scope-slot-tolerance A line scope written inside a path slot (```` ```COPY (worker:///src.md<2,3>) ````)
672
760
  is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
673
761
  before a slot's closing paren can only be a scope; every path slot of a statement is repaired
674
762
  the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
675
763
  statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
676
764
  never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
677
765
  - §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.
766
+ error at the second paren stating the one-slot rule and that a pattern belongs in the
767
+ `[{"pattern": …}]` option; the statement is dropped and its siblings run.
680
768
 
681
- | Prefix | Dialect | Canonical body | Typed admission | Runtime owner |
769
+ | Prefix | Dialect | Canonical form | Typed admission | Runtime owner |
682
770
  |-----------|----------|--------------------------------------|-----------------------------------|---------------------|
683
771
  | `//` | XPath | `//selector` | XPath 1.0 `xpath.parse()` | Mimetype projection |
684
772
  | `/` | Regex | `/pattern/flags` | ECMAScript `RegExp` construction | Mimetype projection |
773
+ | `^` | Regex | `^pattern`, no slashes or flags | ECMAScript `RegExp` construction | Mimetype projection |
685
774
  | `$` | JSONPath | RFC 9535 expression | `json-p3` compilation | Mimetype projection |
686
- | `~` | Semantic | `~phrase` | Any text after the prefix | Embedding index |
775
+ | `~` | Full-text | `~query` | Single-line raw string | SQLite FTS5 index |
687
776
  | `&` | Graph | `&symbol`, `&<symbol`, or `&>symbol` | Exact shape validation | Symbol index |
688
777
  | none | Glob | Shell glob or literal text | Single-line raw string | Mimetype projection |
689
778
 
690
779
  XPath is classified before regex because its prefix is two slashes. Regex
691
780
  splitting respects escapes and character classes; `\/` represents a literal
692
781
  slash. The AST stores regex `pattern` and `flags`, not a compiled object.
693
- Semantic matchers require no parse step. Graph admission validates its direction
782
+ SQLite validates full-text query expressions at execution. Graph admission validates its direction
694
783
  and non-whitespace symbol before runtime. Every other leading character remains
695
784
  in the fallback glob/literal dialect; `@(...)` is therefore an extglob group and
696
785
  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.
786
+ structural output rows, not a reserved matcher prefix. FIND scope selects result
787
+ positions without changing the matcher.
699
788
 
700
789
  AstBuilder validation is compile-only and never evaluates a document. Matcher
701
- evaluation belongs to the runtime's selected mimetype, embedding, or symbol
790
+ evaluation belongs to the runtime's selected mimetype, FTS5, or symbol
702
791
  implementation. A matcher admission error is local to its statement; later
703
792
  statements remain recoverable when their boundaries are trustworthy.
704
793
 
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.
794
+ - §pattern-body-single-line Every matcher is one physical line. On the protocol
795
+ operations it is the heading line's text ({§naked-pattern}) or the `pattern` option's
796
+ JSON string ({§matcher-option}), one line by construction; the client-tier LOOK still carries its matcher as a body, and AstBuilder
797
+ rejects a multiline one before dialect classification. A regex that matches a
798
+ newline uses the two-character `\n` escape. Non-matcher operation bodies remain
799
+ multiline.
712
800
 
713
801
  ## §scope-slot 7. Scope markers
714
802
 
@@ -729,14 +817,14 @@ The operation column names the canonical AST operation after
729
817
  | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
730
818
  | KILL | 0/1/2 text coordinates | Whole target when absent; one physical line or inclusive range when present ({§kill-scope}) |
731
819
  | 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}) |
820
+ | ```` ```TASK ```` | `timeout[,poll]` | Waiting intent: bounded or indefinite wait and optional poll cadence ({§send-wait-scope}) |
821
+ | Directed SEND | Owner-defined numeric scope | Worker actors schedule a task with `delay[,interval]` ({§send-directed-scope}) |
733
822
 
734
823
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
735
824
  whole line, two integers are an inclusive whole-line range, and four integers
736
825
  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
826
+ admit `0` as prepend and `-1` as append. FIND result positions and READ
827
+ text coordinates do not admit decimal scope components. A log KILL intersects a valid body-relative line
740
828
  scope with each selected body; an absent line is a successful no-op for that
741
829
  body, while unsupported arity is a runtime failure.
742
830
 
@@ -765,95 +853,74 @@ reinterpreting them. FIND owns a deterministic result order so the same
765
853
  inclusive range selects the same positions from unchanged state. The parser
766
854
  does not enforce either condition.
767
855
 
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.
856
+ ## 8. Literal programs and code blocks
773
857
 
774
- Delimiter rules:
858
+ A producer carrying literal fences uses an outer backtick count absent from
859
+ standalone fence lines in its body ({§fence-boundary}). The serializer chooses
860
+ a count greater than every backtick run in the body; parsed AST values carry
861
+ no framing state.
775
862
 
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.
863
+ `````text
864
+ ````EDIT (README.md) <1,-1>
865
+ Run the tests:
787
866
 
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.
867
+ ```sh
868
+ npm test
803
869
  ```
870
+ ````
871
+ `````
804
872
 
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.
873
+ The inner shell example is EDIT content, not an EXEC invocation. The same
874
+ rule protects code examples in SEND, WORK, FORK, BARE and every other body.
808
875
 
809
- ## 9. SEND Codes
876
+ ## 9. Turn dispositions
810
877
 
811
- Pathless terminal SEND disposition codes align with HTTP semantics so that model training
812
- transfers directly:
878
+ TASK inventory intent maps to the existing HTTP-shaped lifecycle statuses
879
+ ({§task-inventory-intent}). The runtime adjudicates that intent against actual
880
+ results, obligations and timing:
813
881
 
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 |
882
+ | Intent | Nominal status | Meaning |
883
+ |---|---|---|
884
+ | TASK omitted | 102 | Continue silently, without a receipt or strike for omission |
885
+ | empty, continue, pending | 102 | Continue or recover; an explicit empty inventory is refused with a soft 409 receipt, no strike |
886
+ | wait | 202 | Park when a live obligation or explicit timing exists |
887
+ | complete | 200 | Conclude once execution results permit completion |
888
+ | fail | 499 | End unsuccessfully and cancel unresolved descendant scope |
889
+ | Runtime or infrastructure failure | 5xx | Not a model-authored task status |
820
890
 
821
891
  ### §waitpid-dispositions The terminal contract (waitpid)
822
892
 
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
893
+ The model may supply one current inventory per turn; its statuses determine
894
+ one intention. Without TASK, an operation-bearing turn continues silently.
895
+ The engine verifies an explicit intention against the loop's actual
896
+ obligations (spawned children, open streams, pending results); the grammar
897
+ polices *shape* only. Asking
827
898
  the human is the native `question` EXEC tool ({§question-tool}), not a
828
899
  disposition. The shape rules ARE structural:
829
900
 
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),
901
+ - §send-mid-reservation TASK has a reserved token ({§turn-disposition}).
902
+ A turn admits at most one TASK, anywhere among its operations
903
+ ({§disposition-anywhere}); the runtime executes it last. A second
904
+ disposition is a structural error, not a choice between competing outcomes.
905
+ - §disposition-anywhere The disposition may sit anywhere in a model turn
906
+ (operator, 2026-09-12: models state the plan first; the inventory is a
907
+ statement about state, not a boundary). `PlurnkParser.parse` admits every
908
+ operation before and after it in authored order; the runtime defers only the
909
+ disposition until the other admitted operations settle
910
+ ({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
911
+ position. TASK omission does not synthesize a disposition ({§turn-shape}).
912
+ - SEND is communication: an optional recipient path and an optional body.
913
+ - §park-202-only TASK wait intent applies `<T>` (wait up to T minutes),
841
914
  `<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).
915
+ (indefinite; the join's own liveness bounds it). See §7 for the scope
916
+ slot's shape. Other intents leave timing unapplied
917
+ with a factual warning; timing does not override the inventory's intent.
918
+ - §inventory-only-turn A TASK-only turn is valid for every inventory intent.
919
+ Actionable work does not require an invented OP and does not imply parking.
920
+ Ordinary repetition, strike and execution limits still apply.
921
+
922
+ SEND with no `(path)` responds to the Active Prompts without ending the turn. SEND with
923
+ `(path)` directs the message to that recipient. Neither changes loop status.
857
924
 
858
925
  ### §send-body SEND body projection
859
926
 
@@ -865,78 +932,39 @@ defines no synthetic scheme or READ-back convention for them.
865
932
 
866
933
  ## §parser-architecture 10. Parser architecture
867
934
 
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.
935
+ ANTLR owns framing, slots and statement composition; AstBuilder produces the
936
+ schema-owned AST. Registration, effects and authority remain runtime concerns.
871
937
 
872
938
  ```mermaid
873
939
  stateDiagram-v2
874
940
  [*] --> 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
941
+ DEFAULT --> SLOTS: fenced native OP or executor
942
+ SLOTS --> TARGET: (
943
+ TARGET --> SLOTS: )
944
+ SLOTS --> METADATA: {
945
+ METADATA --> SLOTS: }
946
+ SLOTS --> BODY: header newline or tolerated inline body
947
+ SLOTS --> DEFAULT: matching compact closer
948
+ BODY --> DEFAULT: matching standalone closer, no nested block
949
+ BODY --> BODY: nested literal block or other body content
890
950
  ```
891
951
 
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
952
  ## §whitespace-contract 11. Whitespace and interstatement text
913
953
 
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.
954
+ Body framing removes the header line ending and the single line ending
955
+ immediately before the closing fence. Every other body character is preserved,
956
+ including leading/trailing blank lines, indentation, CRLF and literal
957
+ backslash escapes. A formatter adds its own framing newline even when a body
958
+ already ends in one. Interstatement whitespace belongs to no body.
959
+
960
+ A header starts at column zero; the first operation may follow provider preamble
961
+ without a separating newline. Text outside operation blocks is ignored in every
962
+ parser tier: before, between, and after operations. It produces no AST item,
963
+ message, receipt, or diagnostic. Exact source remains in `ops:///` under
964
+ {§turn-ops-log-curation}; body bytes and source positions are unchanged. A matching
965
+ closer still ends its body, and no missing closer is inferred. No generic Markdown
966
+ rendering, indentation stripping or recursive code-block extraction occurs.
967
+ Only a header aside has aside semantics.
940
968
 
941
969
  ## §public-api 12. Public API
942
970
 
@@ -945,39 +973,34 @@ and wire types come from generated schemas; the small hand-maintained parser
945
973
  types cover ordered parse items and `PlurnkParseError`, which JSON Schema cannot
946
974
  express. Consumers never receive ANTLR parse-tree or token types.
947
975
 
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.
976
+ §turn-shape `PlurnkParser.parse` accepts one model turn. A turn without any
977
+ operation is reported by one hard diagnostic (`no valid Plurnk operation was
978
+ found.`), which the host may admit as an empty turn rather than reject
979
+ (plurnk-core `§empty-turn`). An explicit disposition may sit anywhere in it
980
+ ({§disposition-anywhere}). Omitted TASK
981
+ means silent continuation: no synthesized statement, diagnostic, receipt,
982
+ warning, or strike. The authored operations and source remain unchanged.
983
+ Explicit empty or malformed inventories retain their own handling.
984
+ Unfinished blocks never receive inferred closers.
985
+ Bounded operation errors retain valid siblings. Duplicate dispositions
986
+ and failed document boundaries remain structural failures.
987
+
988
+ `parseLog` reads consecutive saved turns separated by their dispositions and
989
+ requires their dispositions; a saved turn is stored per turn, so a mid-turn
990
+ disposition never needs splitting. There is no outer Markdown program wrapper;
991
+ the executable blocks themselves are the program.
969
992
 
970
993
  §tier-entrypoints Each parser entry point owns one document tier:
971
994
 
972
995
  | Entry point | Accepted document | Result statement type |
973
996
  |--------------------------------|----------------------------------------------------------------|-----------------------|
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` |
997
+ | `PlurnkParser.parse` | One operation-bearing model turn; at most one TASK, anywhere | `PlurnkStatement` |
998
+ | `PlurnkParser.parseStatements` | Zero or more protocol statements | `PlurnkStatement` |
999
+ | `PlurnkParser.parseLog` | One or more consecutive disposition-ended turns | `PlurnkStatement` |
1000
+ | `PlurnkParser.parseClient` | Executable blocks, including the read-shaped LOOK command | `ClientStatement` |
978
1001
 
979
- Every entry point returns ordered `statement`, `error`, and, where admitted,
980
- `text` items. When present, {§unparsed-tail-boundary} governs the result's item
1002
+ Every entry point ignores outside text under {§whitespace-contract} and returns
1003
+ ordered `statement` and `error` items. When present, {§unparsed-tail-boundary} governs the result's item
981
1004
  extent. The statement `op` field discriminates the generated per-operation
982
1005
  union.
983
1006
 
@@ -1242,16 +1265,30 @@ from user-authored prompt content. An adapter may expose no public means to set
1242
1265
  it; Core validates and records it through the same prompt admission path.
1243
1266
 
1244
1267
  §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.
1268
+ origin, immediate parent identity, minted `kind` (`conversation`; `fork` for a
1269
+ child carrying a fork boundary; `work` for any other child), and `lifecycle`,
1270
+ the worker's latest work loop projected through {§loop-lifecycle-vocabulary} (`idle`
1271
+ when it has none). Maintenance-only loops do not change this projection;
1272
+ their ordinary turns and results remain durable history. `readWorker` resolves exactly one id or name and returns
1273
+ `null` when absent. `listWorkers` filters collections by origin or lineage
1274
+ position; an omitted parent filter means every position and an explicit `null`
1275
+ means roots. Singular and plural cardinalities are distinct contracts.
1276
+ Observation is not a client binding or permission grant; a client renders kind
1277
+ and lifecycle, it never infers them.
1278
+
1279
+ §loop-lifecycle-vocabulary One projection maps a loop's durable status onto the
1280
+ lifecycle words every client renders, shared by the status gauge and the worker
1281
+ directory: no loop `idle`; 100 `queued`; 102 `running`; 202 `parked`; 200
1282
+ `completed`; any status of 400 or more `failed` (413 budget, 429 turn ceiling,
1283
+ 499 cancel, 500 fail, 504 execution timeout, 508 runaway). `lifecycleOfLoopStatus`
1284
+ in `@plurnk/plurnk-contracts` is that projection's one owner.
1250
1285
 
1251
1286
  §application-loop-observation Loop observation exposes the durable scheduler
1252
- state, exact terminal `OperationResult`, and exact count of packet-bearing
1287
+ state of work loops (excluding maintenance-only administrative loops), exact terminal `OperationResult`, and exact count of packet-bearing
1253
1288
  Turns for one owned Worker. Packetless producer Turns and physical provider
1254
- retries do not contribute to `packetCount`. Exterior
1289
+ retries do not contribute to `packetCount`. Scheduled tasks expose `scheduledAt`
1290
+ (ISO date), optional `intervalMinutes`, and `recurrenceId` (the original task id).
1291
+ Packet notifications carry the same timing; ordinary tasks omit it. Exterior
1255
1292
  adapters consume this projection instead of reconstructing lifecycle from
1256
1293
  events or persistence; events remain the live notification edge.
1257
1294
 
@@ -1270,13 +1307,14 @@ class PlurnkParseError extends Error {
1270
1307
  readonly column: number;
1271
1308
  readonly source: ErrorSource;
1272
1309
  readonly severity: Severity;
1310
+ readonly code?: "invalid-turn-structure";
1273
1311
  }
1274
1312
  ```
1275
1313
 
1276
1314
  §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
1315
+ statement's `position` identifies the first backtick of its header; a diagnostic
1316
+ identifies the offending or recovery point; `unparsedTail.from` identifies where
1317
+ the undefined tail begins. A
1280
1318
  statement constructed without retained parsed source uses `UNKNOWN_POSITION`,
1281
1319
  the unknown sentinel; its dispatch origin remains a separate fact.
1282
1320
 
@@ -1307,40 +1345,86 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
1307
1345
  the sole and complete owner of syntax-error messaging because it holds the
1308
1346
  parse state, lexer mode, and expected-token set that no consumer has. It
1309
1347
  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
1348
+ turn-shape diagnostics ({§turn-shape}). Omitted TASK produces no diagnostic, and
1349
+ neither does the position of a present one ({§disposition-anywhere}). A failed
1350
+ document boundary carries `code: "invalid-turn-structure"`, which cannot be
1351
+ recovered as an individual failed operation. Source with no
1313
1352
  parsed operation yields `no valid Plurnk operation was found.` Targeted
1314
1353
  diagnostics are:
1315
1354
 
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.
1332
- - §label-recipient-redirect **A label beside a recipient.** `### SEND0 (TERM)
1333
- (worker://parent)` and `### SEND0 (worker://parent) (TERM)` are one parser error at
1334
- the heading: `a (NEXT|WAIT|TERM|FAIL) SEND names no recipient; message a recipient
1335
- with its own SEND first` ({§send-label}).
1336
- - §misplaced-annotation-advisory **Annotation in the body.** A READ or FIND whose
1355
+ - §inline-flag-tolerance **PCRE inline modifiers.** A regex whose pattern opens with
1356
+ `(?i)`, `(?m)`, `(?s)` or a combination the pretrained spelling of a flag, which
1357
+ ECMAScript refuses as an invalid group is read with those letters lifted into
1358
+ its flags (`/(?i)shutdown|reactor/` is `/shutdown|reactor/i`; `^(?i)note:` is the
1359
+ anchored regex with `i`), with one warning-severity advisory naming the flag
1360
+ position after the closing `/`. Only a leading group of `i`, `m` and `s` lifts;
1361
+ `(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched. From
1362
+ the 2026-09-13 dumbox demo pass, where the model wrote the inline form, was
1363
+ refused, and rewrote it as a trailing flag one turn later.
1364
+ - §regex-trailing-text A valid `/pattern/flags` prefix followed by horizontal
1365
+ whitespace and trailing text receives one concise trailing-content
1366
+ diagnostic, with or without flags, without assuming what the extra text was
1367
+ intended to represent. Invalid patterns or flags retain the native
1368
+ regex failure; no branch silently removes or executes trailing content.
1369
+ - §naked-pattern **The matcher rides the heading bare.** After the path, and any
1370
+ scope or option block, the rest of a FIND, READ or KILL heading line is the
1371
+ matcher, in whichever dialect its first characters claim
1372
+ ({§matcher-prefix-claims}): `/re/i`, `^anchored`, `//xpath`, `$.json`, `~words`,
1373
+ `&symbol`, or a sigil-less glob or literal such as `TODO` or `*.ts`. Those
1374
+ operations take no body, so heading-line text can mean nothing else. On EDIT only
1375
+ a sigil lifts, because plain heading-line text is the replacement body it always
1376
+ was; the lines beneath the heading are then the replacement, and none deletes each
1377
+ match ({§edit-pattern}). A trailing `<!-- aside -->` on the same line stays the
1378
+ aside. The lift is exactly what `[{"pattern": "…"}]` produces, and that option
1379
+ remains the escape for a matcher the heading cannot hold bare: one opening with
1380
+ `(`, `<`, `[` or a backtick, one containing `<!--`, and every COPY/MOVE operand.
1381
+ `FIND (src/parser.ts) /\bparse\w+\b/` is the same operation as
1382
+ `FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
1383
+ dialect without slashes or flags: the whole text is the pattern, so
1384
+ `READ (reasoning:///1/1) ^NOTE:.*` selects a turn's note lines (operator,
1385
+ 2026-09-12: "Recursive Reasoning").
1386
+ - §trailing-slots **Slots after the matcher peel off the right.** The heading text after
1387
+ the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
1388
+ shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
1389
+ trailing `[option block]` that parses as an array of objects come off the right end in
1390
+ any order, each taken once and only when the heading did not already carry that slot,
1391
+ until what remains is the matcher. `READ (a.rs) /fn resolve_/ <1,-1> <!-- entities -->`
1392
+ is the same operation as `READ (a.rs) <1,-1> /fn resolve_/ <!-- entities -->`, with
1393
+ one warning-severity advisory naming the canonical order for the scope or block
1394
+ (operator, 2026-09-13: "swallow up anything that passes as legitimate plurnk"; the
1395
+ 2026-09-13 dumbox run refused three headings for this in one turn). A matcher that
1396
+ itself ends in one of those shapes takes the option escape.
1397
+ - §matcher-body-redirect **A body beneath those headings.** Text below the heading
1398
+ of a FIND, READ or KILL is a body, and those operations take none: the builder
1399
+ keeps the statement without it and raises one warning-severity advisory (`READ
1400
+ takes no body; the body was ignored. A pattern belongs on the opening fence line
1401
+ after the path.`), delivered like {§misplaced-aside-advisory} as a
1402
+ `parse_advisory` notice (operator, 2026-09-12: a gentle warning, never an error
1403
+ the model must recover from). One sigil line beneath the heading is the bare form
1404
+ written a line low and still lifts; nothing else is promoted into a matcher from
1405
+ below the heading, and the advisory never echoes the body.
1406
+ - §combined-anchor-tolerance **Combined anchor and line number in a scope.** A
1407
+ text-coordinate scope position written `@hash:L` or `@hash L` is the displayed
1408
+ `@abcde 42:` prefix copied whole (a koota-entity turn refused nine of them in a
1409
+ row, 2026-09-12): the position is the anchor, the number is dropped, and one
1410
+ warning-severity advisory names the anchor-only form. The scope lexes as one
1411
+ ordinary marker at any text-coordinate operation, either COPY/MOVE operand
1412
+ included; nothing cascades.
1413
+ - §invalid-scope-diagnostic **Malformed scope content.** After a properly spaced
1414
+ scope opener, report the offending scope (at most 64 code points, ending at
1415
+ `>` or the heading's line end) and its operation's constraint: FIND result
1416
+ positions, EXEC/TASK minutes, text coordinates, or no scope. Do not append advice for
1417
+ other operations or infer why the producer supplied the value. Spacing and
1418
+ boundary-loss diagnostics retain their own contracts.
1419
+ - §misplaced-aside-advisory **Aside in the body.** A READ or FIND whose
1337
1420
  body is solely an HTML comment (`<!-- … -->`) can never carry a matcher: it is
1338
- the annotation the model put on the line below the heading. The builder takes
1339
- the comment as the annotation when the heading has none, builds the operation
1421
+ the aside the model put on the line below the heading. The builder takes
1422
+ the comment as the aside when the heading has none, builds the operation
1340
1423
  with no body, and raises one warning-severity advisory stating that observed
1341
1424
  normalization; the parser places the advisory right after its statement and
1342
1425
  the service delivers it as a `parse_advisory` notice with its position. A body
1343
- with any other content is a matcher, as before.
1426
+ with any other content is ignored under the same advisory path
1427
+ ({§matcher-body-redirect}).
1344
1428
 
1345
1429
  §error-shape The diagnostic class determines how much guidance the parser may
1346
1430
  provide:
@@ -1352,18 +1436,20 @@ provide:
1352
1436
  | Non-fatal advisory | `severity: "warning"` | One narrowly gated likely mistake and canonical alternative; input remains admitted. |
1353
1437
  | Boundary loss | `unparsedTail` | Where trust ends, which header slot remains open, and why later input is undefined. |
1354
1438
 
1355
- All messages use PLURNK protocol vocabulary: heading, lane, signal, target,
1439
+ All messages use PLURNK protocol vocabulary: opening fence, closing fence, target,
1356
1440
  scope, line marker, body, section boundary, or space between slots. They never
1357
1441
  expose ANTLR rule or token names. They refer to a slot or
1358
1442
  feature rather than an implementation rule. Generic tutoring, speculative
1359
1443
  intent, coordinate restatement, and multiple repair strategies are forbidden.
1444
+ Unexpected top-level text immediately after a closed operation identifies that
1445
+ operation's opening line, closing line, and matching backtick count.
1360
1446
 
1361
1447
  Examples of canonical hard facts:
1362
1448
 
1363
1449
  - `unrecognized character '<' in target`
1364
- - `unrecognized character ':' in signal`
1450
+ - `unexpected bracket modifier; the fence name selects the executor`
1365
1451
  - `unrecognized character 'X' in statement header`
1366
- - `a turn must begin with \`## PLAN0\``
1452
+ - `TASK's body begins below the header`
1367
1453
  - `expected ')'; got ':'`
1368
1454
 
1369
1455
  Each malformed statement produces at most one hard error. The first recorded
@@ -1374,8 +1460,9 @@ Independent malformed statements each retain one hard error. Advisories remain
1374
1460
  separate because they do not represent failed admission.
1375
1461
 
1376
1462
  §unparsed-tail-boundary When the lexer cannot determine where a malformed
1377
- statement ends, the result's `unparsedTail` marks the position from which
1378
- parsing gave up. `ParseResult.items` contains only facts that begin strictly
1463
+ statement ends an unfinished `(target` or `[metadata` slot on a heading line —
1464
+ the result's `unparsedTail` marks the position from which parsing gave up. A block
1465
+ without a closer is not such a case: it ends under {§closer-fallback}. `ParseResult.items` contains only facts that begin strictly
1379
1466
  before that point; recovered contexts and diagnostics at or beyond it are not
1380
1467
  public results. The tail is one separate boundary fact, not an additional
1381
1468
  malformed-statement diagnostic. Consumers must treat anything from that point
@@ -1398,6 +1485,6 @@ runtime constructs this; the parser provides the fields):
1398
1485
  "column": 12,
1399
1486
  "source": "parser",
1400
1487
  "severity": "error",
1401
- "message": "target slot of `### READ0` opened at line 1 but never closed - add `)`"
1488
+ "message": "READ block opened at line 1 but was not closed with 3 backticks"
1402
1489
  }
1403
1490
  ```