@plurnk/plurnk-contracts 1.18.0 → 1.19.1

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 (60) hide show
  1. package/.env.defaults +11 -0
  2. package/README.md +2 -2
  3. package/SPEC.md +325 -265
  4. package/dist/conformance/agui-v1.json +3 -3
  5. package/dist/schema/ClientStatement.json +0 -10
  6. package/dist/schema/LoopPolicy.json +32 -3
  7. package/dist/schema/LoopPolicyRequest.json +16 -0
  8. package/dist/schema/McpConfigurationOverlay.json +1 -14
  9. package/dist/schema/PlurnkStatement.json +20 -31
  10. package/dist/schema/ProposalProjection.json +2 -2
  11. package/dist/schema/SkillDefinition.json +3 -3
  12. package/dist/schema/TextLineMarker.json +1 -1
  13. package/dist/src/ApplicationPort.d.ts +37 -10
  14. package/dist/src/ApplicationPort.d.ts.map +1 -1
  15. package/dist/src/LoopLifecycle.d.ts +5 -0
  16. package/dist/src/LoopLifecycle.d.ts.map +1 -1
  17. package/dist/src/LoopLifecycle.js +15 -0
  18. package/dist/src/LoopLifecycle.js.map +1 -1
  19. package/dist/src/MessageResource.d.ts +30 -0
  20. package/dist/src/MessageResource.d.ts.map +1 -0
  21. package/dist/src/MessageResource.js +2 -0
  22. package/dist/src/MessageResource.js.map +1 -0
  23. package/dist/src/PlurnkParseError.d.ts +1 -3
  24. package/dist/src/PlurnkParseError.d.ts.map +1 -1
  25. package/dist/src/PlurnkParseError.js +1 -4
  26. package/dist/src/PlurnkParseError.js.map +1 -1
  27. package/dist/src/Problems.d.ts +1 -0
  28. package/dist/src/Problems.d.ts.map +1 -1
  29. package/dist/src/Problems.js +19 -1
  30. package/dist/src/Problems.js.map +1 -1
  31. package/dist/src/TurnDisposition.d.ts +2 -3
  32. package/dist/src/TurnDisposition.d.ts.map +1 -1
  33. package/dist/src/TurnDisposition.js +4 -20
  34. package/dist/src/TurnDisposition.js.map +1 -1
  35. package/dist/src/Validator.d.ts +3 -3
  36. package/dist/src/Validator.d.ts.map +1 -1
  37. package/dist/src/Validator.js +14 -15
  38. package/dist/src/Validator.js.map +1 -1
  39. package/dist/src/index.d.ts +3 -4
  40. package/dist/src/index.d.ts.map +1 -1
  41. package/dist/src/index.js +4 -4
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/types.d.ts +5 -5
  44. package/dist/src/types.d.ts.map +1 -1
  45. package/dist/src/types.generated.d.ts +32 -67
  46. package/dist/src/types.generated.d.ts.map +1 -1
  47. package/dist/src/types.js +12 -10
  48. package/dist/src/types.js.map +1 -1
  49. package/package.json +4 -3
  50. package/plurnk.md +39 -66
  51. package/dist/schema/AcpPlan.json +0 -63
  52. package/dist/schema/Plan.json +0 -65
  53. package/dist/src/AcpPlanValue.d.ts +0 -6
  54. package/dist/src/AcpPlanValue.d.ts.map +0 -1
  55. package/dist/src/AcpPlanValue.js +0 -36
  56. package/dist/src/AcpPlanValue.js.map +0 -1
  57. package/dist/src/PlanValue.d.ts +0 -9
  58. package/dist/src/PlanValue.d.ts.map +0 -1
  59. package/dist/src/PlanValue.js +0 -63
  60. package/dist/src/PlanValue.js.map +0 -1
package/SPEC.md CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  ## 1. Overview
4
4
 
5
- §contract-authority This package is the single authority for PLURNK's language, schemas, generated
6
- types, parser, and runtime-neutral wire envelopes. Its package root
7
- is the single code API for those contracts.
5
+ This package is the single authority for PLURNK's language, schemas, generated types and
6
+ runtime-neutral wire envelopes; `@plurnk/plurnk-parser` implements the language it specifies
7
+ ({§parser-consumers}). Its package root is the single code API for those contracts
8
+ ({§root-value-api}).
8
9
 
9
10
  | Surface | Canonical export or artifact |
10
11
  | ------------------------------------------------------------------------------- | --------------------------------------------------- |
11
- | Parser, AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
12
- | Capability and loop policies with their defaults | `CapabilityPolicy`, `LoopPolicy`, `DEFAULT_CAPABILITY_POLICY`, `DEFAULT_LOOP_POLICY` |
12
+ | AST, validators, Problems, results, Notices, text regions and extents | `@plurnk/plurnk-contracts` |
13
+ | Capability and loop policies | `CapabilityPolicy`, `LoopPolicy`, `LoopPolicyRequest`, `PROPOSAL_POLICIES` |
13
14
  | Durable reasoning intent | `ReasoningPolicy`, `REASONING_POLICIES` |
14
15
  | Model route and catalog discovery | `ModelRoute`, `ModelCatalogQuery`, `ModelCatalogPage`, `ModelReadiness` |
15
16
  | Stopped-world client contract | `ProposalDisposition`, `ProposalProjection` |
@@ -89,12 +90,10 @@ and previews; never reformat literal resources, JSONL framing, or wire/evidence
89
90
  serialization. Compact aggregate rows ({§json-result-rendering}) and packet
90
91
  metadata retain their deliberate layouts.
91
92
 
92
- ## §contract-layers 1.1 Contract layers and admission boundary
93
+ ## 1.1 Contract layers and admission boundary
93
94
 
94
- PLURNK uses one contract with deliberately different projections. A tolerant
95
- ingester accepting a spelling does not make that spelling canonical model
96
- teaching, and an operator's sampling grammar admitting a sentence does not
97
- make its runtime semantics valid.
95
+ One contract, deliberately different projections (ARCHITECTURE.md). Each layer
96
+ below owns what it alone can decide.
98
97
 
99
98
  ```mermaid
100
99
  flowchart LR
@@ -127,7 +126,7 @@ WHATWG `URL`, ECMAScript `RegExp`, XPath 1.0, and RFC 9535 JSONPath parsers.
127
126
  The runtime owner decides facts that require state or operation-specific
128
127
  meaning, including registered scheme resolution, target existence, tag
129
128
  selection, text-region bounds, result ordering, full-text ranking, mutation
130
- effects, executor behavior, and numeric operation-code semantics.
129
+ effects, and executor behavior.
131
130
 
132
131
  ### §contract-proposal-projection Loop policy and stopped-world projection
133
132
 
@@ -168,11 +167,15 @@ workspace layer and their normalized intersection: `service`, `workspace`, and
168
167
  capability policy or inherited bound; every actor uses the same live workspace
169
168
  policy. A client never derives effective authority from the mutable layer alone.
170
169
 
171
- §loop-policy `DEFAULT_CAPABILITY_POLICY` and `DEFAULT_LOOP_POLICY` are the
172
- contracts-owned complete defaults. A loop policy is immutable after creation;
173
- its `proposals` field chooses one downstream settlement posture, independently
174
- of workspace capability policy. Capability
175
- admission precedes effect classification and proposal settlement.
170
+ §loop-policy A `LoopPolicy` is complete and immutable after creation:
171
+ `proposals` chooses one downstream settlement posture and `attended` says
172
+ whether anyone can answer, independently of workspace capability policy. An
173
+ unattended loop cannot hold a proposal for review, so the schema refuses that
174
+ pair. A `LoopPolicyRequest` is the part of a policy its creator chose to state.
175
+ Contracts hold no default for the rest: the daemon's panel supplies it
176
+ ({§loop-policy-composition}). `PROPOSAL_POLICIES` is the schema-owned
177
+ vocabulary of `proposals`. Capability admission precedes effect
178
+ classification and proposal settlement.
176
179
 
177
180
  §reasoning-policy-wire `ReasoningPolicy` is exactly `off | adaptive | low |
178
181
  medium | high`. The schema owns this shared wire vocabulary. Providers own the
@@ -264,37 +267,56 @@ input
264
267
  `````
265
268
 
266
269
  §section-boundary Every statement is one backtick block. Its header occupies one
267
- physical line: a fence of at least three backticks, an optional numeric delimiter
270
+ physical line: a fence of at least four backticks ({§four-backtick-operations}), an optional numeric delimiter
268
271
  ({§numeric-delimiter}), then the name and its slots. A closer is shown by
269
272
  convention and never demanded ({§fence-closer}, {§closer-fallback}). There are no
270
- operation suffixes or heading levels. Nothing in the language is counted by the
271
- author: every boundary is an anchored line the parser recognizes by its first
272
- characters (operator, 2026-09-12: counted pairs failed 52 of 363 turns on
273
- GLM-5.3-flash; anchored tokens failed none).
273
+ operation suffixes or heading levels. Complete nested matches take precedence
274
+ over local recovery ({§balanced-fences}).
274
275
 
275
276
  §fence-closer A block opened with N backticks and delimiter D (its digits, possibly
276
- none) closes at the first line at column zero made of at least N backticks,
277
+ none) closes at the first unclaimed line made of at least N backticks,
277
278
  exactly D, and nothing else but horizontal whitespace. Count follows CommonMark: a
278
279
  shorter fence inside the body is body; an equal or longer bare fence closes a bare
279
- block. The delimiter compares exactly: a bare fence never closes a delimited block,
280
+ block unless it closes a balanced nested block ({§balanced-fences}). The delimiter
281
+ compares exactly: a bare fence never closes a delimited block,
280
282
  and a delimited fence never closes a bare one. The compact one-line form closes on
281
283
  its heading line after the modifiers under the same rule.
282
284
 
285
+ §balanced-fences A complete nested interpretation takes precedence over missing-closer
286
+ recovery. Within an undelimited body, line-leading labeled fences open literal
287
+ blocks; their matching closers close the innermost block first. The enclosing
288
+ block and its nested blocks must all close, with matching widths and delimiters
289
+ under {§fence-closer}. Equal opener/closer totals alone are insufficient. A complete
290
+ inline block is already closed; a numerically delimited block is opaque until its
291
+ own closer. Preserve every nested body byte, including apparent OPs and known
292
+ executors, without dispatching them. If no complete enclosing interpretation exists,
293
+ retain {§fence-heading-in-body} and {§closer-fallback}. These rules apply equally to
294
+ model programs, stored programs, client operations, and reasoning quotations.
295
+
283
296
  §numeric-delimiter Digits between the opening backticks and the name (an opener
284
- carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it. This is how a
285
- block nests fences of its own width: with a delimiter, a body may carry bare fences
286
- and headings of the same count. The delimiter is syntax, never AST or persistence
297
+ carrying `42EDIT (x)`) identify the block, and only a fence carrying `42` closes it.
298
+ This explicitly protects bare fences and headings, including incomplete examples.
299
+ The delimiter is syntax, never AST or persistence
287
300
  state; `PlurnkParser.frame` chooses one when the body it wraps holds a heading line
288
301
  of four or more backticks ({§statement-rendering}).
289
302
 
290
- §fence-heading-in-body A fence line of four or more backticks, optional digits, and
291
- a name that is a native operation or a known executor is a heading wherever it
292
- stands. Inside an open block it ends that block without closing it
303
+ §four-backtick-operations **An operation opens with four backticks.** A heading is a fence of
304
+ four or more backticks; a three-backtick fence is markdown wherever it stands, so an answer's
305
+ code blocks (```` ```sh ````, ```` ```ts ````) are prose and never run (operator,
306
+ 2026-09-18, #761). A three-backtick fence naming an operation or known executor draws one warning that it
307
+ needs four backticks; any other three-backtick fence draws none. `plurnk.md` teaches exactly
308
+ four; longer fences are tolerated, not taught. Every producer of a statement — the parser, a
309
+ client composing `/look`, a client's tab-completion — writes `PLURNK_FENCE` rather than its own
310
+ literal, so no surface can ship a width the parser will quote (plurnk/plurnk#92).
311
+
312
+ §fence-heading-in-body Outside a complete nested block ({§balanced-fences}), a fence
313
+ line of four or more backticks, optional digits, and a name that is a native operation
314
+ or a known executor is a heading. Inside an open block it ends that block without closing it
293
315
  ({§closer-fallback}) and opens the next statement. Fence lines of fewer than four
294
- backticks are headings only outside any block. Known executors are `sh` plus what
316
+ backticks are never headings ({§four-backtick-operations}). Known executors are `sh` plus what
295
317
  the host names in `ParseOptions.executors`. Consequences: a closer glued to the next
296
- opener (eight backticks then `READ`) can never swallow the rest of a turn, and a quoted
297
- heading of four or more backticks inside a body needs the numeric delimiter to stay body.
318
+ opener (eight backticks then `READ`) can never swallow an unbalanced turn, and a numeric
319
+ delimiter preserves quoted headings even when their own fences are incomplete.
298
320
 
299
321
  §closer-fallback A block that ends at a heading or at the end of the input has no
300
322
  closer of its own. Its body is cut back to its last bare fence line (any count,
@@ -303,23 +325,27 @@ ending goes with it; when no bare fence line exists the body is the whole span l
303
325
  one terminating line ending. This carries no diagnostic: a missing closer is never
304
326
  an admission failure, and {§unparsed-tail-boundary} is not involved.
305
327
 
306
- §fence-boundary Inside a body, fences are read by count and delimiter, never by
307
- name, except for the heading rule above:
328
+ §fence-boundary Balanced nesting is resolved before local recovery. Otherwise,
329
+ fences are read by count and delimiter, except for the heading rule above:
308
330
 
309
331
  | Fence encountered inside a body | Meaning |
310
332
  |---|---|
333
+ | Part of a complete nested block | Literal body, including its openers and closers |
311
334
  | Fewer backticks than the block's own | Body |
312
335
  | At least the block's backticks, bare, block undelimited | The block's closer |
313
336
  | At least the block's backticks carrying the block's delimiter | The block's closer |
314
337
  | At least the block's backticks with any other delimiter | Body |
315
338
  | Four or more backticks naming a native operation or known executor | A heading: ends the block, opens the next statement |
316
339
 
317
- §indented-fences Leading horizontal whitespace before an opener or a closer is
318
- not part of the fence: an indented fence line is a fence line, on openers,
319
- closers, headings that end a block, and the closer fallback. A body keeps its own
320
- lines' indentation. CommonMark allows three spaces; this allows any, because a
321
- model that indents an emission indents all of it (operator, 2026-09-12: measured
322
- at five to ten percent of emissions on GLM-5.3-flash).
340
+ §indented-fences Leading horizontal whitespace before a CLOSER is not part of the fence: an
341
+ indented closer, heading-that-ends-a-block, or closer fallback still closes, and a body keeps its
342
+ own lines' indentation. An OPENER is different: an operation's backticks follow a newline
343
+ directly (operator, 2026-09-18), so an indented fence opens a quotation, never an operation —
344
+ CommonMark reads an indented block as code, and `plurnk.md` shows its own examples that way. This
345
+ reverses the 2026-09-12 tolerance (then measured at five to ten percent of emissions on
346
+ GLM-5.3-flash; 2.8% of that lane's emissions today). The cost is paid loudly: an indented fence
347
+ naming a known operation draws `must start its line to run` and is an operation attempt, never an
348
+ answer ({§prose-conclusion}), so the loop continues instead of delivering a program as prose.
323
349
 
324
350
  §inline-chain A closer on a heading line, or on a body's closing line, may be
325
351
  followed on that same line by the next opener; the closer still closes, and the
@@ -331,7 +357,7 @@ text there is the heading's own and is read under {§transparent-inline-closer}.
331
357
  closing fence on a heading line followed by more of that heading — a `<scope>`, an
332
358
  `[option block]`, a `<!-- aside -->`, or a naked matcher — does not end the reading: the
333
359
  heading keeps taking its slots under {§trailing-slots} and {§naked-pattern}, exactly as though
334
- the closer were absent, so ```READ (a.md)``` `<1,2>` is the same operation as `READ (a.md) <1,2>`.
360
+ the closer were absent, so ````` ````READ (a.md)```` ````` `<1,2>` is the same operation as `READ (a.md) <1,2>`.
335
361
  The closer is still a closer: the block ends with that physical line and never reaches down for
336
362
  the next operation, which is what a bare heading carrying a matcher would do. A closer followed
337
363
  by the next opener is {§inline-chain}, and by nothing is the ordinary {§fence-closer}. There is
@@ -344,20 +370,22 @@ registered executor's name case-insensitively opens that executor (`SH` opens
344
370
  `sh`), and the statement's `executor` is the registered spelling, so a lookup
345
371
  by that name never misses. Operation names stay uppercase by teaching and were
346
372
  never written otherwise in 12,000 fences; one `SH` in 1,300 `sh` fences was
347
- (2026-09-13 census). An unregistered name in any case is still prose
348
- ({§interstitial-fence}).
349
-
350
- §one-line-turn **A whole turn on one line.** The most frequent private rejection
351
- across the 2026-09-12/13 dumbox runs (five of eleven) was a turn emitted as a
352
- single line: prose, then heading after heading with no line ending anywhere. Two
373
+ (2026-09-13 census). An unregistered name without an accepted spelling
374
+ ({§executor-js-spelling}) is still prose ({§interstitial-fence}).
375
+
376
+ §executor-js-spelling When `node` is registered and `js` is not, `js` names
377
+ `node` under {§executor-case}. Parsing normalizes the runtime before admission
378
+ and dispatch; execution, policy, receipts, and output addresses remain Node's.
379
+ The authored source remains unchanged. This spelling adds no executor, discovery
380
+ entry, or model-facing teaching. An explicitly registered `js` retains its own
381
+ identity; absent `node`, the shorthand grants no executable capability.
382
+
383
+ §one-line-turn **A whole turn on one line.** A model may emit a turn as a single
384
+ line: prose, then heading after heading with no line ending anywhere. Two
353
385
  rules absorb it. The next opener on a heading's own line, after the heading's
354
386
  slots, ends that heading's block bodyless and opens ({§empty-section}), so
355
387
  `````EDIT (a.rs) <60,66> <!-- drop --> ````EDIT (b.rs) <31,38>` is two scoped
356
- deletions; and a TASK whose inventory rides its heading line as a `[…]` block
357
- (`````TASK [{"content": "…", "status": "in_progress"}] ````) takes that block as
358
- its body when nothing sits beneath the heading, with one warning-severity
359
- advisory naming the body as where the inventory belongs. A block beneath the
360
- heading still wins.
388
+ deletions. Literal inline bodies follow {§heading-inline-body}.
361
389
 
362
390
  §anchor-digits In a text scope, `@` followed by one to four digits cannot be a
363
391
  hash and is read as that line number, with one warning-severity advisory naming
@@ -367,22 +395,69 @@ the five-character anchor form. Five characters after `@` are always an anchor.
367
395
  line takes the rest of the line as the aside, with one warning-severity advisory.
368
396
  A closed aside followed by more text is unchanged.
369
397
 
370
- §interstitial-fence A fence line that names no native operation and no known
371
- executor opens nothing: unlabeled, or tagged like a code block (`ts`, `json`),
372
- outside a block it is prose and ignored like every other outside line
373
- ({§whitespace-contract}); inside a body it is body. Nothing is promoted into a
374
- header or recursively parsed. There is no implicit SEND: a reply is an explicit
375
- `SEND` block. (This replaces the retired unlabeled-fence SEND of the fences
376
- chapter, whose unlabeled fences turned displaced headings into silent messages.)
398
+ §quotation **A fence that opens no operation quotes.** Outside a body, a line-start fence that
399
+ is not an operation heading — unlabeled, tagged like a code block (`ts`, `json`), three
400
+ backticks ({§four-backtick-operations}), indented, or four or more with an unknown name — opens a
401
+ quotation that runs to its matching closer (same character, width at least the opener's) or to
402
+ the end of the input. Everything inside is data: no operation runs there, native tool-call
403
+ markup is not read ({§native-tool-calls}), and no heading draws an advisory. So a model may show
404
+ plurnk's own operations in an answer. Three exceptions keep programs whole:
405
+ CommonMark's own rule that a backtick opener's line carries no further backtick, so
406
+ ```` ```READ (x)``` ```` is inline code and quotes nothing after it; a bare fence directly under a
407
+ line carrying a fence run, which is an orphaned closer and quotes nothing; and a tag that is a
408
+ missed operation — a known name under four backticks or off column zero, or an unknown name at
409
+ operation width — which still draws one warning (`markdown` and `md` are polite envelopes and
410
+ draw none). There is no implicit SEND: a reply is prose ({§prose-conclusion}) or an explicit
411
+ `SEND` block. Origin (#767, 2026-09-18): under prose answers, a quoted example executed.
412
+
413
+ §interstitial-fence Superseded by {§quotation}: an unlabeled fence no longer opens nothing, it
414
+ quotes. (It in turn replaced the retired unlabeled-fence SEND of the fences chapter, whose
415
+ unlabeled fences turned displaced headings into silent messages.)
416
+
417
+ §closer-aside A closing fence followed on its line by one aside and nothing else
418
+ is the closer; the aside is outside text. Read as body, that line would be written
419
+ into the edited resource (#758: a recorded EDIT deleting a line wrote
420
+ ```` ```` <!-- remove duplicated Result import --> ```` into a Python file). A
421
+ closing fence followed by any other text is still body.
422
+
423
+ §heading-slot-order A heading near-miss with exactly one reading is read as that
424
+ reading, with no diagnostic and no teaching (#758):
425
+
426
+ - an aside written before the heading's remaining scope or JSON option block is
427
+ read after them (`READ (a.md) <!-- why --> <1,-1>`); an aside followed by a
428
+ target or a non-JSON block keeps its place and stays refused;
429
+ - zero-width characters (U+200B–U+200D, U+2060, U+FEFF) on a heading line are skipped;
430
+ - a matcher that begins with a sigil and is quoted in single backticks
431
+ (`` `^def test_` ``) is that matcher; quoted text without a sigil stays refused.
432
+
433
+ Tokens keep their source positions; only their order in the stream changes.
434
+
435
+ §native-tool-calls An emission that yields no operation may be a model's native
436
+ tool-call markup (DeepSeek's `<||DSML|| calls>` block) naming a plurnk operation or
437
+ a known executor. Each `invoke` is read as that operation's canonical fence:
438
+ `path`/`target` fill the target, `scope`/`range`/`lines` the scope, `pattern` a
439
+ matcher option, `aside` the aside, and `body`/`content`/`command` or plain lines
440
+ inside the invoke the body; plurnk slots written after the invoke name are kept.
441
+ Each block keeps its line count, so statement positions still name the source
442
+ line. An invoke with an unknown name or parameter leaves the whole emission as it
443
+ was. An emission that already yields an operation is never rewritten. No
444
+ diagnostic, notice or teaching mentions the reading (#760).
445
+
446
+ §operation-attempt An emission with no operation is either prose, which a host may take
447
+ as the model's answer, or an operation attempt. `PlurnkParser.operationAttempt(input,
448
+ executors)` names the attempt: a line opening a four-backtick fence, a heading outside
449
+ any fence ({§bare-heading-advisory}; a known executor's name is a heading only when a
450
+ slot follows it), native tool-call markup that {§native-tool-calls} did not read, or
451
+ echoed packet rows (`### log://…`). Anything else, three-backtick code blocks included,
452
+ is prose (#761).
377
453
 
378
454
  §bare-heading-advisory An operation name that opens a line outside any block in the
379
- shape of a heading (`READ (…)`, `TASK`, …) is prose and runs nothing. The parser
455
+ shape of a heading (`READ (…)`, `NOTE`, …) is prose and runs nothing. The parser
380
456
  emits one warning-severity advisory naming the fence form, placed after the parsed
381
457
  items, so the loss is never quiet.
382
458
 
383
459
  §empty-section Both the compact bodyless form and an empty multiline block
384
- normalize optional bodies to null. TASK normalizes an empty body to `[]`
385
- under {§plan-value}. Closing fences are conventional, never required
460
+ normalize optional bodies to null. Closing fences are conventional, never required
386
461
  ({§closer-fallback}).
387
462
 
388
463
  §statement-rendering `PlurnkParser.stringify` renders native OP names and named
@@ -393,7 +468,8 @@ It chooses at least four backticks and more than any run within the body, and a
393
468
  numeric delimiter whenever the body holds a heading line of four or more backticks
394
469
  ({§fence-heading-in-body}), preserving body bytes on reparse. Fence length and
395
470
  delimiter are syntax, not AST or persistence state. Core-authored programs use
396
- this serializer and the ordinary admission parser.
471
+ this serializer and the ordinary admission parser. Rendering preserves a matcher
472
+ beside owner metadata, not only a matcher carried inside its `pattern` option.
397
473
 
398
474
  | Element | Contract |
399
475
  |---|---|
@@ -412,8 +488,9 @@ adjacent slots and scope/metadata permutations within a selection without
412
488
  changing ownership or making them distinct canonical forms. Each selection
413
489
  has at most one scope; its metadata blocks retain their authored order.
414
490
 
415
- §plan-slotless TASK accepts no target or metadata. Its optional scope carries
416
- waiting timing; its inventory body begins below the header.
491
+ §lifecycle-slots NOTE accepts no target, scope, or metadata. WAIT retains its
492
+ optional target and ignores syntactically valid scope and metadata without diagnostics
493
+ ({§send-wait-scope}). Their literal bodies begin below the header.
417
494
 
418
495
  §heading-inline-body Nonempty body text belongs below the fence header.
419
496
  The ingester tolerates body text after horizontal whitespace on the header,
@@ -427,7 +504,7 @@ routing, timing, or body input. Comments inside a body remain literal except
427
504
  for the narrowly owned {§misplaced-aside-advisory}.
428
505
 
429
506
  §scheme-metadata-modifier A target may carry one single-line `[metadata]`
430
- block after its scope; an executor fence also admits it without a target.
507
+ block after its scope; executor and SEND fences also admit it without a target.
431
508
  Read with its brackets, the block is a JSON array of option objects, merged
432
509
  left to right with later keys winning; the keys belong to the selected scheme
433
510
  or executor, which owns interpretation, validation and authority. The language
@@ -469,7 +546,7 @@ whose block left no metadata back bare when the bare form reads back identically
469
546
 
470
547
  | Element | Shape or role |
471
548
  |---|---|
472
- | Native OP | `FIND READ EDIT COPY MOVE SEND BARE WORK FORK KILL TASK` |
549
+ | Native OP | `FIND READ EDIT COPY MOVE SEND BARE WORK FORK KILL NOTE WAIT` |
473
550
  | Executor name | Letters, digits, `_`, `.`, `+`, or `-`; reserved OPs win |
474
551
  | Fence | Three or more backticks, matched by exact count |
475
552
  | `(path)` | Local path, URI, program or tool name; §5 |
@@ -494,58 +571,27 @@ governed by {§canonical-statement}; runtime conditions remain explicit below.
494
571
  | WORK | optional fresh `worker://name`, or a prompt resource ({§worker-spawn-prompt-resource}) | none | prompt; optional with a resource |
495
572
  | FORK | optional context-inheriting `worker://name`, or a prompt resource | none | prompt; optional with a resource |
496
573
  | KILL | required target, including a log item | optional text region ({§kill-scope}) | none; the matcher is the `pattern` option |
497
- | SEND | optional recipient | optional recipient timing | message |
498
- | TASK | none | optional timeout and poll for waiting intent | Plurnk Plan JSON array |
499
-
500
- §operation-code-polymorphism Operation-result statuses and turn dispositions are
501
- distinct facts. TASK derives lifecycle intent from its inventory;
502
- SEND and KILL carry no disposition operand.
503
-
504
- §plan-value **TASK carries the complete current task inventory.** Admission
505
- parses one JSON array in any whitespace layout, including
506
- {§json-result-rendering}, strips unknown entry keys, and validates string
507
- `content` and native `status`. Opaque `_meta` remains optional. Nonempty plain
508
- text, malformed JSON, or an invalid array becomes one `in_progress` entry
509
- containing the exact body, with one factual warning. No partial repair or list
510
- inference occurs. A blank body becomes `[]`, never inferred completion.
511
- The normalized array is the sole semantic value in AST, persistence and model
512
- log; exact authored bytes remain in `turnOps`. Earlier inventories are history,
513
- not accumulated obligations. Task descriptions are not executable dependencies.
514
-
515
- §task-inventory-intent The first matching row determines intent, independently
516
- of entry order. Actual execution adjudicates intent under {§wait-obligation-matrix}.
517
-
518
- | Inventory condition | Intent | Derived lifecycle status |
519
- |---|---|---|
520
- | TASK omitted | Continue silently | 102 |
521
- | Explicit empty inventory | Recover empty inventory | 102 |
522
- | Any `in_progress` | Continue independent actionable work | 102 |
523
- | Any `waiting`, no `in_progress` | Await work or an event | 202 |
524
- | Any `todo`, no actionable or waiting entry | Continue | 102 |
525
- | All terminal, any `completed` | End successfully | 200 |
526
- | All `failed`, nonempty | End unsuccessfully | 499 |
527
-
528
- `todo` is not started; `in_progress` can be actively advanced;
529
- `waiting` awaits an ongoing stream, worker or external event. `completed` is
530
- successful resolution; `failed` is unsuccessful resolution. A failed sibling
531
- does not terminate independent unfinished work. The engine does not infer a
532
- dependency graph from task text.
533
-
534
- §plan-acp-projection **Only an ACP-facing boundary projects the model-native
535
- Plan.** It constructs ACP's `{ "entries": [...] }` Plan object from the internal
536
- array, synthesizes the ACP-required neutral `medium` priority on every entry
537
- (the model-native Plan carries none). The internal value is never mutated.
538
- Native `todo` maps to ACP `pending`: the same state under ACP's name, so it carries no marker.
539
- Native `waiting` maps to ACP `in_progress`, with `Waiting:` and a space prepended
540
- to its display content; native `failed` maps to ACP `completed`, with `Failed:` and a space.
541
- Both carry their native status in `_meta["plurnk.xyz/status"]`, an edge-owned
542
- key derived from the native status rather than trusted from authored metadata.
543
- Other statuses and unrelated metadata remain unchanged. The labels preserve
544
- meaning even when a generic client ignores extension metadata.
545
- The projected value validates against the separately owned ACP Plan schema pinned
546
- to ACP v1
547
- [`schema-v1.21.0`](https://github.com/agentclientprotocol/agent-client-protocol/tree/schema-v1.21.0)
548
- commit `272bf799f35a258c6a4107a0410ed361e83683d3`.
574
+ | SEND | optional recipient | recipient-defined; none for workers ({§send-directed-scope}) | message |
575
+ | NOTE | none | none | literal working memory |
576
+ | WAIT | optional event source ({§send-wait-scope}) | ignored | explanation of the wait |
577
+
578
+ §note-value NOTE retains its literal body as ordinary model-owned working memory.
579
+ It has no target, scope, metadata, or lifecycle effect. Its full body participates
580
+ in ordinary log token accounting and model-driven curation; prior notes are not
581
+ automatically hidden. A NOTE-only turn is subject to ordinary conclusion,
582
+ repetition and strike rules.
583
+
584
+ §reasoning-notes NOTE is the only operation admitted from exposed provider
585
+ reasoning. The shared fence parser selects line-leading NOTE statements in a
586
+ quotation-preserving reasoning context: other backtick or tilde code blocks are
587
+ opaque, and operation-heading recovery cannot escape them. Blockquoted and inline
588
+ examples are not headings. Program parsing is unchanged; other reasoned operations
589
+ never execute.
590
+ Selected notes precede the content program in the admitted turn and use the
591
+ ordinary dispatcher, persistence and log projection. Reasoning bytes and content
592
+ bytes remain separate, unchanged forensic sources. Rejected or superseded
593
+ provider attempts cannot commit notes. Non-thinking models use NOTE in their
594
+ ordinary program. No task inventory is inferred from notes or lifecycle prose.
549
595
 
550
596
  §exec-executor-slot The fence name selects the executor directly: for example,
551
597
  `python3 (tools/report.py)` or `gitea (issue_list)` on the opening fence line.
@@ -559,21 +605,29 @@ shell examples name `sh` explicitly.
559
605
  The path names a program or tool and is never split. Metadata such as
560
606
  `[{"cwd": "…"}]` remains interpreted by the selected executor.
561
607
 
562
- §turn-disposition TASK is the sole workflow declaration. `TurnDisposition`
563
- derives intent from its canonical inventory under {§task-inventory-intent}.
564
- The AST has no independently settable lifecycle status, target or metadata.
565
- SEND deliberately messages its recipient, or the user when targetless; it
566
- neither changes task status nor terminates a run. A program contains one final
567
- TASK, not last-wins competing inventories. Former lifecycle names are not aliases.
568
-
569
- §send-wait-scope TASK accepts `<timeout[,poll]>` in whole minutes. It applies
570
- only to a waiting intent ({§park-202-only}); the dispatcher validates its bounds.
571
- Otherwise it is unused, with a factual warning rather than a changed outcome.
572
-
573
- §send-directed-scope A recipient SEND preserves an optional numeric scope after
574
- its target and metadata. The addressed owner assigns its semantics; worker
575
- actors use `<delay[,interval]>` ({§worker-scheduled-send}). A targetless message
576
- takes no scope. Scheduling does not change the message body or disposition.
608
+ §turn-disposition WAIT requests parking; its literal body does not control
609
+ scheduling, and its optional target is the label the row keeps, never a join;
610
+ the AST has no independently settable lifecycle status or metadata.
611
+ A turn admits any number of WAITs, all deferred until its other operations
612
+ settle and together one park. End-of-program adjudication owns continuation, joining and completion
613
+ under {§wait-obligation-matrix}; no terminal verb or synthetic receipt is required.
614
+ SEND delivers messages and NOTE retains memory, neither declaring an outcome.
615
+
616
+ §send-wait-scope WAIT's optional target is retained as the row's label; no
617
+ scheme handler runs for it, so every WAIT is the bare park. Scope and metadata
618
+ are discarded without diagnostics,
619
+ including structured scopes. The body, aside, and exact submitted program
620
+ remain intact. WAIT neither creates a schedule nor restricts which ordinary
621
+ events may awaken the loop. A scope slot's content is skipped unread whatever it
622
+ holds (`<sh:///…>` included; #756). Ordinary malformed-header rules still apply;
623
+ a second WAIT is one more label on the same park, never a refusal.
624
+
625
+ §send-directed-scope A recipient SEND carries an optional numeric scope after
626
+ its target and metadata through to the addressed owner, which assigns its
627
+ semantics or refuses it; worker actors refuse one (`scope-unsupported`, 400),
628
+ because later and recurring delivery belong to the schedule family. A
629
+ targetless message takes no scope. A scope never changes the message body or
630
+ disposition.
577
631
 
578
632
  §kill-scope KILL takes an optional text-coordinate scope beside its target, numeric or
579
633
  anchored (```` ```KILL (log:///**/READ) <17,-1>``` ```` or
@@ -638,8 +692,8 @@ parses as `{ kind: "local", raw: "data/users.html", fragment: "readable" }`: the
638
692
  the path and the rest is the channel (a spelling that opens with `#` names no path and stays
639
693
  whole), exactly as `worker:///a.html#readable` decomposes, so
640
694
  the `#channel` a READ receipt advertises (`channels: {"#readable": N}`) is addressable in the
641
- same bare spelling the receipt used (2026-09-13 dumbox demo: the model appended it and was
642
- told no entry existed at `users.html#readable`). `raw` is therefore always the path alone; a
695
+ same bare spelling the receipt used: a model that appends the channel to the path it was just
696
+ shown addresses the same entry. `raw` is therefore always the path alone; a
643
697
  bare path never contains a literal `#`, and `PlurnkParser.stringify` renders the channel back.
644
698
  Without a `#` the field is absent, so an older `LocalPath` literal stays valid.
645
699
 
@@ -664,7 +718,7 @@ Mutation semantics:
664
718
  no body. Each selection binds its own target, scope, matcher, and metadata
665
719
  under {§slot-order}, {§matcher-option}, and {§scheme-metadata-modifier}.
666
720
 
667
- ### §operation-observation Per-operation observations
721
+ ### Per-operation observations
668
722
 
669
723
  | OP | Successful observation |
670
724
  |------|-----------------------------------------------------------------------------------|
@@ -679,7 +733,7 @@ Mutation semantics:
679
733
  | WORK | Spawn acknowledgement; the deliverable arrives through the log |
680
734
  | FORK | Spawn acknowledgement; the inherited worker's deliverable arrives through the log |
681
735
  | KILL | Status of deletion or termination |
682
- | TASK | Current inventory and adjudicated lifecycle outcome |
736
+ | NOTE / WAIT | Literal memory or wait explanation |
683
737
 
684
738
  §find-result-unit For FIND, authored target shape fixes the paginated result
685
739
  unit. An exact target with a matcher pages flat match locations; a glob or
@@ -696,7 +750,7 @@ EDIT. Whole-channel transfers remain bodyless structural effects; runtime
696
750
  owners reject binary markers rather than treating a text field as a byte lane.
697
751
 
698
752
  Every operation returns the runtime-neutral `OperationResult` defined by
699
- {§operation-result}. Its `status` belongs to the result envelope; TASK supplies
753
+ {§operation-result}. Its `status` belongs to the result envelope; a lifecycle operation supplies
700
754
  the authored lifecycle intent. Durable operation observations are projected into a later packet;
701
755
  retrieval never returns inline within the emitting turn.
702
756
 
@@ -753,11 +807,11 @@ never target content. Glob metacharacters remain legal path data.
753
807
  Matching and folder-scope semantics remain runtime concerns.
754
808
 
755
809
  §worker-name The exported `WORKER_NAME` contract governs names minted for URI
756
- authority slots: a lowercase DNS label matching
757
- `[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?`. There is no reserved-name list: the
758
- runtime's own actor is named `_plurnk`, a spelling the predicate never admits,
759
- and `~` is the sole current-worker sigil, likewise outside the mintable
760
- alphabet. Every matching value, including `self` and `plurnk`, is an ordinary
810
+ authority slots: `[A-Za-z0-9][A-Za-z0-9_-]{0,62}` (1–63 ASCII characters,
811
+ starting with a letter or digit). Case is preserved and significant;
812
+ `Approach_A` and `approach_a` are distinct names. There is no reserved-name list: the
813
+ runtime's own actor is named `_plurnk`, a spelling the predicate never admits.
814
+ Every matching value, including `self` and `plurnk`, is an ordinary
761
815
  literal worker name. This is a minting and registry invariant, not an
762
816
  ingestion restriction: the parser decomposes arbitrary URL authorities.
763
817
 
@@ -774,17 +828,17 @@ matching.
774
828
  statement-level error the parser discards the rest of that statement and resumes at the
775
829
  next heading; the turn shape is decided locally (a turn disposition is recognized by its own
776
830
  token, never by a whole-turn alternative), so one malformed heading costs one
777
- diagnostic and every later statement, the turn disposition included, stands on its own. Any
778
- other second path slot names the one-slot rule.
831
+ diagnostic and every later statement, the turn disposition included, stands on its own.
779
832
  - §scope-slot-tolerance A line scope written inside a path slot (```` ```COPY (worker:///src.md<2,3>) ````)
780
833
  is read as `(worker:///src.md) <2,3>` — `<` and `>` are not URI characters, so a `<…>` right
781
834
  before a slot's closing paren can only be a scope; every path slot of a statement is repaired
782
835
  the same way — and the slip is one warning-severity advisory at the `<`, placed right after its
783
836
  statement, stating the `(path) <scope>` form that was used. The statement runs; a warning is
784
837
  never a strike. A `<` anywhere else in the slot remains the lexer's refusal.
785
- - §second-path-slot A second `(path)` on a heading that already closed one is a parser
786
- error at the second paren stating the one-slot rule and that a pattern belongs in the
787
- `[{"pattern": …}]` option; the statement is dropped and its siblings run.
838
+ - §extra-path-slot A path slot beyond the operation's admitted operands is a parser
839
+ error at its opening paren. Report the unexpected slot and the grammar's expected
840
+ alternatives when available, without inferring pattern intent or imposing another
841
+ operation's operand count. The statement is dropped and its siblings run.
788
842
 
789
843
  | Prefix | Dialect | Canonical form | Typed admission | Runtime owner |
790
844
  |-----------|----------|--------------------------------------|-----------------------------------|---------------------|
@@ -837,8 +891,8 @@ The operation column names the canonical AST operation after
837
891
  | COPY/MOVE destination | 0/1/2/4 text coordinates after target | Region replaced or insertion point at the destination |
838
892
  | KILL | 0/1/2 text coordinates | Whole target when absent; one physical line or inclusive range when present ({§kill-scope}) |
839
893
  | execution | `timeout[,poll]` | Spawn lifetime bound and poll cadence in minutes |
840
- | ```` ```TASK ```` | `timeout[,poll]` | Waiting intent: bounded or indefinite wait and optional poll cadence ({§send-wait-scope}) |
841
- | Directed SEND | Owner-defined numeric scope | Worker actors schedule a task with `delay[,interval]` ({§send-directed-scope}) |
894
+ | WAIT | None | Scope is ignored ({§send-wait-scope}) |
895
+ | Directed SEND | Owner-defined numeric scope | Carried to the addressed owner; worker actors refuse it ({§send-directed-scope}) |
842
896
 
843
897
  Text coordinates use the algebra in {§text-scope-semantics}: one integer is a
844
898
  whole line, two integers are an inclusive whole-line range, and four integers
@@ -854,8 +908,7 @@ its `L`, `SL`, or `EL` position denotes a line. Columns, prepend `0`, and append
854
908
  `-1` remain numeric. Exact READ, EDIT, COPY/MOVE source and destination,
855
909
  KILL, and client LOOK preserve these positions in `TextLineMarker`; core resolves them
856
910
  against the addressed current text before operation-specific numeric scope
857
- semantics run. A matcher-bearing or path-glob READ normalizes to FIND, whose
858
- result positions remain numeric and reject anchors. Numeric text scopes remain
911
+ semantics run. FIND's result positions remain numeric and reject anchors. Numeric text scopes remain
859
912
  canonical and fully supported. Parser acceptance does not imply model-facing
860
913
  recommendation.
861
914
 
@@ -895,51 +948,44 @@ rule protects code examples in SEND, WORK, FORK, BARE and every other body.
895
948
 
896
949
  ## 9. Turn dispositions
897
950
 
898
- TASK inventory intent maps to the existing HTTP-shaped lifecycle statuses
899
- ({§task-inventory-intent}). The runtime adjudicates that intent against actual
900
- results, obligations and timing:
951
+ The runtime adjudicates a nonempty admitted program against actual messages,
952
+ results and live obligations ({§wait-obligation-matrix}). A response with no
953
+ operation receives empty-turn recovery, not successful completion ({§empty-turn}).
901
954
 
902
955
  | Intent | Nominal status | Meaning |
903
956
  |---|---|---|
904
- | TASK omitted | 102 | Continue silently, without a receipt or strike for omission |
905
- | empty, continue, todo | 102 | Continue or recover; an explicit empty inventory is refused with a soft 409 receipt, no strike |
906
- | wait | 202 | Park when a live obligation or explicit timing exists |
907
- | complete | 200 | Conclude once execution results permit completion |
908
- | fail | 499 | End unsuccessfully and cancel unresolved descendant scope |
957
+ | Unanswered messages or unobserved results | 102 | Continue silently |
958
+ | WAIT | 202 | Park when a live obligation exists; otherwise continue at 102 |
959
+ | All messages answered, live work remains | 202 | Join the held work |
960
+ | All messages answered, results observed, no held work | 200 | The admitted program concludes; no repeated response is required |
961
+ | KILL own worker | 499 | Cancel unfinished work in that worker and its descendants |
909
962
  | Runtime or infrastructure failure | 5xx | Not a model-authored task status |
910
963
 
911
- ### §waitpid-dispositions The terminal contract (waitpid)
964
+ ### The terminal contract (waitpid)
912
965
 
913
- The model may supply one current inventory per turn; its statuses determine
914
- one intention. Without TASK, an operation-bearing turn continues silently.
915
- The engine verifies an explicit intention against the loop's actual
916
- obligations (spawned children, open streams, pending results); the grammar
917
- polices *shape* only. Asking
966
+ The model may supply one WAIT per turn. The host, not the grammar, owns
967
+ turn boundaries and adjudicates the loop's actual obligations. Asking
918
968
  the human is the native `question` executor tool ({§question-tool}), not a
919
969
  disposition. The shape rules ARE structural:
920
970
 
921
- - §send-mid-reservation TASK has a reserved token ({§turn-disposition}).
922
- A turn admits at most one TASK, anywhere among its operations
923
- ({§disposition-anywhere}); the runtime executes it last. A second
924
- disposition is a structural error, not a choice between competing outcomes.
925
- - §disposition-anywhere The disposition may sit anywhere in a model turn
926
- (operator, 2026-09-12: models state the plan first; the inventory is a
927
- statement about state, not a boundary). `PlurnkParser.parse` admits every
971
+ - §send-mid-reservation WAIT is reserved ({§turn-disposition}).
972
+ A turn admits any number of lifecycle declarations, anywhere among its
973
+ operations ({§disposition-anywhere}); the runtime executes them last, as one park.
974
+ - §disposition-anywhere A disposition may sit anywhere in a model turn
975
+ without imposing a program boundary. `PlurnkParser.parse` admits every
928
976
  operation before and after it in authored order; the runtime defers only the
929
- disposition until the other admitted operations settle
977
+ dispositions until the other admitted operations settle
930
978
  ({§op-execution-order}). Nothing is dropped and no diagnostic is raised for
931
- position. TASK omission does not synthesize a disposition ({§turn-shape}).
979
+ position. Omission does not synthesize a disposition ({§turn-shape}).
932
980
  - SEND is communication: an optional recipient path and an optional body.
933
- - §park-202-only TASK wait intent applies `<T>` (wait up to T minutes),
934
- `<T,P>` (adds a poll cadence, mirroring the execution slot), `<-1>`
935
- (indefinite; the join's own liveness bounds it). See §7 for the scope
936
- slot's shape. Other intents leave timing unapplied
937
- with a factual warning; timing does not override the inventory's intent.
938
- - §inventory-only-turn A TASK-only turn is valid for every inventory intent.
939
- Actionable work does not require an invented OP and does not imply parking.
981
+ - §park-202-only WAIT joins live work: an open stream or a live
982
+ child. With none, it continues. It takes no scope ({§send-wait-scope});
983
+ a future message is scheduled through the schedule family.
984
+ - §lifecycle-only-turn A WAIT-, SEND-, or NOTE-only turn is valid.
985
+ NOTE does not request parking or acknowledge messages.
940
986
  Ordinary repetition, strike and execution limits still apply.
941
987
 
942
- SEND with no `(path)` responds to the Active Prompts without ending the turn. SEND with
988
+ SEND with no `(path)` answers the open messages without ending the turn. SEND with
943
989
  `(path)` directs the message to that recipient. Neither changes loop status.
944
990
 
945
991
  ### §send-body SEND body projection
@@ -953,7 +999,7 @@ defines no synthetic scheme or READ-back convention for them.
953
999
  ## §parser-architecture 10. Parser architecture
954
1000
 
955
1001
  The implementation this section describes lives in `@plurnk/plurnk-parser`
956
- ({§parser-boundary}); this section remains the contract it implements.
1002
+ ({§parser-consumers}); this section remains the contract it implements.
957
1003
 
958
1004
  ANTLR owns framing, slots and statement composition; AstBuilder produces the
959
1005
  schema-owned AST. Registration, effects and authority remain runtime concerns.
@@ -961,11 +1007,13 @@ schema-owned AST. Registration, effects and authority remain runtime concerns.
961
1007
  ```mermaid
962
1008
  stateDiagram-v2
963
1009
  [*] --> DEFAULT
1010
+ DEFAULT --> QUOTATION: a fence that opens no operation
1011
+ QUOTATION --> DEFAULT: its matching closer
964
1012
  DEFAULT --> SLOTS: fenced native OP or executor
965
1013
  SLOTS --> TARGET: (
966
1014
  TARGET --> SLOTS: )
967
- SLOTS --> METADATA: {
968
- METADATA --> SLOTS: }
1015
+ SLOTS --> METADATA: [
1016
+ METADATA --> SLOTS: ]
969
1017
  SLOTS --> BODY: header newline or tolerated inline body
970
1018
  SLOTS --> DEFAULT: matching compact closer
971
1019
  BODY --> DEFAULT: matching standalone closer, no nested block
@@ -983,13 +1031,13 @@ already ends in one. Interstatement whitespace belongs to no body.
983
1031
  A header starts at column zero; the first operation may follow provider preamble
984
1032
  without a separating newline. Text outside operation blocks is ignored in every
985
1033
  parser tier: before, between, and after operations. It produces no AST item,
986
- message, receipt, or diagnostic. Exact source remains in `ops:///` under
1034
+ message, receipt, or diagnostic. Exact source remains in `ops://<worker>/` under
987
1035
  {§turn-ops-log-curation}; body bytes and source positions are unchanged. A matching
988
1036
  closer still ends its body, and no missing closer is inferred. No generic Markdown
989
1037
  rendering, indentation stripping or recursive code-block extraction occurs.
990
1038
  Only a header aside has aside semantics.
991
1039
 
992
- ## §public-api 12. Public API
1040
+ ## 12. Public API
993
1041
 
994
1042
  The package root is the single JavaScript and TypeScript entry point. Shared AST
995
1043
  and wire types come from generated schemas; the small hand-maintained parser
@@ -1000,26 +1048,23 @@ express. Consumers never receive ANTLR parse-tree or token types.
1000
1048
  operation is reported by one hard diagnostic (`no valid Plurnk operation was
1001
1049
  found.`), which the host may admit as an empty turn rather than reject
1002
1050
  (plurnk-core `§empty-turn`). An explicit disposition may sit anywhere in it
1003
- ({§disposition-anywhere}). Omitted TASK
1004
- means silent continuation: no synthesized statement, diagnostic, receipt,
1051
+ ({§disposition-anywhere}). An omitted WAIT produces no synthesized statement, diagnostic, receipt,
1005
1052
  warning, or strike. The authored operations and source remain unchanged.
1006
- Explicit empty or malformed inventories retain their own handling.
1007
1053
  Unfinished blocks never receive inferred closers.
1008
- Bounded operation errors retain valid siblings. Duplicate dispositions
1009
- and failed document boundaries remain structural failures.
1054
+ Bounded operation errors retain valid siblings, and so does a lost boundary:
1055
+ the statements that closed before `unparsedTail.from` are facts, and only what
1056
+ follows is undefined ({§unparsed-tail-boundary}).
1010
1057
 
1011
- `parseLog` reads consecutive saved turns separated by their dispositions and
1012
- requires their dispositions; a saved turn is stored per turn, so a mid-turn
1013
- disposition never needs splitting. There is no outer Markdown program wrapper;
1014
- the executable blocks themselves are the program.
1058
+ The host records programs per turn; no operation acts as a separator between
1059
+ saved programs. There is no outer Markdown program wrapper; the executable
1060
+ blocks themselves are the program.
1015
1061
 
1016
1062
  §tier-entrypoints Each parser entry point owns one document tier:
1017
1063
 
1018
1064
  | Entry point | Accepted document | Result statement type |
1019
1065
  |--------------------------------|----------------------------------------------------------------|-----------------------|
1020
- | `PlurnkParser.parse` | One operation-bearing model turn; at most one TASK, anywhere | `PlurnkStatement` |
1066
+ | `PlurnkParser.parse` | One operation-bearing model turn; at most one lifecycle declaration, anywhere | `PlurnkStatement` |
1021
1067
  | `PlurnkParser.parseStatements` | Zero or more protocol statements | `PlurnkStatement` |
1022
- | `PlurnkParser.parseLog` | One or more consecutive disposition-ended turns | `PlurnkStatement` |
1023
1068
  | `PlurnkParser.parseClient` | Executable blocks, including the read-shaped LOOK command | `ClientStatement` |
1024
1069
 
1025
1070
  Every entry point ignores outside text under {§whitespace-contract} and returns
@@ -1027,26 +1072,28 @@ ordered `statement` and `error` items. When present, {§unparsed-tail-boundary}
1027
1072
  extent. The statement `op` field discriminates the generated per-operation
1028
1073
  union.
1029
1074
 
1030
- §root-value-api The package-root runtime namespace is closed and consists of the
1031
- following supported consumer values. All other root exports are TypeScript types.
1032
-
1033
- | Root value(s) | Consumer contract | Exact owner |
1034
- |---------------------------------------|---------------------------------------------------------------------|---------------------------------------------|
1035
- | `PlurnkParser` | Four document-tier entry points listed above | {§parser-architecture}, {§tier-entrypoints} |
1036
- | `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
1037
- | `parsePath` | Parser-equivalent target admission | {§path-syntax}, {§tier-entrypoints} |
1038
- | `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
1039
- | `Validator` | Validation and assertion against the owning JSON Schemas | {§wire-entrypoint} |
1040
- | `InvalidNoticeError` | Typed failure from `Validator.assertNotice` | {§notice} |
1041
- | `InvalidProblemDetailsError` | Typed failure from `Validator.assertProblemDetails` | {§problem-details} |
1042
- | `InvalidProblemProjectionError` | Typed failure from `Validator.assertProblemProjection` | {§problem-projection} |
1043
- | `InvalidOperationResultError` | Typed failure from `Validator.assertOperationResult` | {§operation-result} |
1044
- | `InvalidTextRegionError` | Typed failure from `Validator.assertTextRegion` | {§text-region} |
1045
- | `InvalidRangeExtentError` | Typed failure from `Validator.assertRangeExtent` | {§range-extent} |
1046
- | `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
1047
- | `PLURNK_OPS` | Runtime tuple from which the closed `PlurnkOp` union is derived | {§canonical-statement} |
1048
- | `WORKER_NAME` | Authority minting predicate | {§worker-name} |
1049
- | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
1075
+ §root-value-api The package-root runtime namespace is closed. Its exact membership has one home,
1076
+ `src/index.test.ts`, which fails on any addition or removal; the table names the families and
1077
+ their owners. All other root exports are TypeScript types. `PlurnkParser` and `parsePath` are not
1078
+ among them: the parser is `@plurnk/plurnk-parser`'s ({§parser-consumers}).
1079
+
1080
+ | Root value(s) | Consumer contract | Exact owner |
1081
+ |-----------------------------------------------------|---------------------------------------------------------------------|-------------------------------------------|
1082
+ | `Validator` and one `Invalid…Error` per assertion | Validation and typed failure against the owning JSON Schemas | {§wire-entrypoint} |
1083
+ | `Problems` | RFC 9457 Problem construction and model projection | {§problem-details}, {§problem-projection} |
1084
+ | `PlurnkParseError` | JSON-serializable positioned parser diagnostic | {§parse-diagnostics} |
1085
+ | `PathSyntax` | Target-slot spelling and exact-versus-glob classification | {§path-parentheses}, {§path-glob} |
1086
+ | `TurnDisposition` | The one turn disposition and its recognition | {§turn-disposition} |
1087
+ | `CapabilityAdmission` | Admission of a capability descriptor against policy layers | {§capability-admission} |
1088
+ | `PLURNK_OPS`, `INTERNAL_ROW_OPS`, `PLURNK_FENCE` | The closed operation alphabet and the language's fence | {§canonical-statement} |
1089
+ | `PROPOSAL_POLICIES` | The vocabulary a loop policy chooses from | {§loop-policy} |
1090
+ | `WORKER_NAME` | Authority minting predicate | {§worker-name} |
1091
+ | `UNKNOWN_POSITION` | Frozen sentinel for an AST statement without retained parsed source | {§parser-position} |
1092
+
1093
+ The remaining values are small pure helpers over those contracts (`isExecution`, `writtenOp`,
1094
+ `lifecycleOfLoopStatus`, `selectWorkerLoop`, `renderJsonResult`, `formatJsonDocument`,
1095
+ `aguiConformanceReport`) and the closed name patterns and vocabularies (`RUNTIME_TAG`,
1096
+ `SKILL_NAME`, `REASONING_POLICIES`).
1050
1097
 
1051
1098
  §parser-construction-boundary Parser construction components are internal rather
1052
1099
  than alternate consumer entry points:
@@ -1056,20 +1103,10 @@ than alternate consumer entry points:
1056
1103
  | `AstBuilder` | Consumes generated ANTLR contexts; `PlurnkParser` and `parsePath` own its API |
1057
1104
  | `PlurnkErrorStrategy`, `RecordingListener` | Assemble parser recovery and diagnostics around `antlr4ng`; consumers receive `PlurnkParseError` values |
1058
1105
 
1059
- ### CLI
1060
-
1061
- ```text
1062
- plurnk-contracts [file] parse a file, or standard input when omitted
1063
- plurnk-contracts --help show usage
1064
- ```
1065
-
1066
- The CLI prints the parse result as JSON. It exits `0` when no error item or
1067
- `unparsedTail` exists and `1` otherwise.
1068
-
1069
1106
  ## 13. Runtime-neutral wire contracts
1070
1107
 
1071
1108
  §wire-entrypoint The package root exports generated wire types, `Problems`, and
1072
- `Validator` alongside the parser and AST. Their owning JSON Schemas are published
1109
+ `Validator` alongside the AST types. Their owning JSON Schemas are published
1073
1110
  through `@plurnk/plurnk-contracts/schema/*.json`, not re-exported as root values.
1074
1111
 
1075
1112
  ### §text-region 13.1 Text regions
@@ -1187,6 +1224,19 @@ Internal invariant violations throw and preserve their cause. An external
1187
1224
  protocol may require its own error envelope; its adapter maps that envelope to
1188
1225
  or from the canonical Problem without creating another PLURNK failure contract.
1189
1226
 
1227
+ §problem-error-carrier `Problems.fromError(error)` recognizes existing failure
1228
+ carriers without depending on a producer's exception class:
1229
+
1230
+ | Carrier | Interpretation |
1231
+ |---------|----------------|
1232
+ | `error.result` present | Validate the complete {§operation-result}; return its Problem, if any |
1233
+ | Otherwise, `error.problem` present | Validate and return {§problem-details} unchanged |
1234
+ | Absent or malformed carrier | Return `null`; the caller retains the original exception as an unexpected failure |
1235
+
1236
+ Recognition never derives recovery text from an exception message, changes its
1237
+ status, or rescues a malformed result through a second Problem field. Unexpected
1238
+ accessor or validator exceptions propagate.
1239
+
1190
1240
  §problem-projection `ProblemProjection` is the sole compact model-packet view of
1191
1241
  an exact `ProblemDetails`. `Problems.project(problem, context)` validates both
1192
1242
  representations and rejects a status that contradicts the enclosing row.
@@ -1248,22 +1298,23 @@ client ID plus symbolic secret, or neither for server-advertised Dynamic Client
1248
1298
  Registration fallback. A definition cannot combine those identity modes.
1249
1299
 
1250
1300
  §mcp-configuration-overlay `McpConfigurationOverlay` is the bounded raw
1251
- configuration projection a client may carry to MCP list and enable actions. It
1252
- contains only string-valued `PLURNK_MCP_*` server declaration variables;
1253
- service-owned connection/request timeouts and default enabledness are excluded.
1301
+ configuration projection a client may carry to MCP list and enable actions: its
1302
+ string-valued `PLURNK_MCP_*` variables, whole. Which of those names are the
1303
+ host's own controls is the host's fact alone — its parser skips every control
1304
+ it owns, so a carried timeout or enabled list has no effect and no client or
1305
+ contract restates that vocabulary.
1254
1306
  The client does not interpret this map. The MCP host composes it over the
1255
1307
  lower normalized definition through the same parser that admits service
1256
1308
  environment declarations, then validates the resulting
1257
1309
  `McpServerDefinition`. Carrying the overlay does not connect, persist, or
1258
1310
  expand credentials by itself.
1259
1311
 
1260
- `SkillDefinition` is the one definition the Worker `skills` Functionality
1261
- family accepts and persists: the standard Agent Skills `name` (the directory
1262
- name), the universal root `scope` (`project` or `global`), and — for a
1263
- Worker-installed skill — the standard installer package `source` that
1264
- provides it. `Validator.assertSkillDefinition` is the family's admission
1265
- boundary; the filesystem under the scope's root, never the definition, is the
1266
- truth about installation.
1312
+ `SkillDefinition` is the one definition the workspace `skills` Functionality
1313
+ family accepts and persists: the standard `name`, source `scope`, and optional
1314
+ installer `source`. `Validator.assertSkillDefinition` validates the wire shape;
1315
+ the schema's name grammar is exposed as `SKILL_NAME` for loaders and discovery
1316
+ ({§agent-skills-name}). Core owns installation truth and lifecycle
1317
+ ({§skills-functionality}).
1267
1318
 
1268
1319
  `A2aAgentDefinition` is the one definition the Worker `a2a` Functionality
1269
1320
  family accepts and persists: the local alias `name` (the `a2a://<name>`
@@ -1287,11 +1338,22 @@ interaction, and event owners through this port.
1287
1338
  from user-authored prompt content. An adapter may expose no public means to set
1288
1339
  it; Core validates and records it through the same prompt admission path.
1289
1340
 
1341
+ `runLoop.attachments` carries typed bytes selected by the adapter;
1342
+ `runLoop.envelope` retains opaque protocol evidence under
1343
+ {§message-envelope-evidence}. `readMessages` projects durable inbox messages
1344
+ and successful conversation replies independently of log visibility, retaining
1345
+ each reply's `answers` addresses (empty for incoming messages).
1346
+ `resolveClientInteraction` may carry the accepted answer's message evidence;
1347
+ Core validates the resolution, retains the arrival, then resumes the operation.
1348
+
1290
1349
  §application-worker-observation Worker observation exposes durable identity,
1291
1350
  origin, immediate parent identity, minted `kind` (`conversation`; `fork` for a
1292
1351
  child carrying a fork boundary; `work` for any other child), and `lifecycle`,
1293
- the worker's latest work loop projected through {§loop-lifecycle-vocabulary} (`idle`
1294
- when it has none). Maintenance-only loops do not change this projection;
1352
+ the worker's representative work loop projected through {§loop-lifecycle-vocabulary} (`idle`
1353
+ when it has none). `selectWorkerLoop` chooses running before parked before queued;
1354
+ ties select the oldest unresolved sequence. With no live work, the latest terminal
1355
+ settlement wins (sequence breaks equal timestamps). Newer terminal history never
1356
+ hides unfinished work. Maintenance-only loops do not change this projection;
1295
1357
  their ordinary turns and results remain durable history. `readWorker` resolves exactly one id or name and returns
1296
1358
  `null` when absent. `listWorkers` filters collections by origin or lineage
1297
1359
  position; an omitted parent filter means every position and an explicit `null`
@@ -1309,9 +1371,7 @@ in `@plurnk/plurnk-contracts` is that projection's one owner.
1309
1371
  §application-loop-observation Loop observation exposes the durable scheduler
1310
1372
  state of work loops (excluding maintenance-only administrative loops), exact terminal `OperationResult`, and exact count of packet-bearing
1311
1373
  Turns for one owned Worker. Packetless producer Turns and physical provider
1312
- retries do not contribute to `packetCount`. Scheduled tasks expose `scheduledAt`
1313
- (ISO date), optional `intervalMinutes`, and `recurrenceId` (the original task id).
1314
- Packet notifications carry the same timing; ordinary tasks omit it. Exterior
1374
+ retries do not contribute to `packetCount`. Exterior
1315
1375
  adapters consume this projection instead of reconstructing lifecycle from
1316
1376
  events or persistence; events remain the live notification edge.
1317
1377
 
@@ -1330,7 +1390,6 @@ class PlurnkParseError extends Error {
1330
1390
  readonly column: number;
1331
1391
  readonly source: ErrorSource;
1332
1392
  readonly severity: Severity;
1333
- readonly code?: "invalid-turn-structure";
1334
1393
  }
1335
1394
  ```
1336
1395
 
@@ -1368,10 +1427,8 @@ and 3.30.2](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html).
1368
1427
  the sole and complete owner of syntax-error messaging because it holds the
1369
1428
  parse state, lexer mode, and expected-token set that no consumer has. It
1370
1429
  produces the final diagnostic message, deduplicated expected-token lists, and
1371
- turn-shape diagnostics ({§turn-shape}). Omitted TASK produces no diagnostic, and
1372
- neither does the position of a present one ({§disposition-anywhere}). A failed
1373
- document boundary carries `code: "invalid-turn-structure"`, which cannot be
1374
- recovered as an individual failed operation. Source with no
1430
+ turn-shape diagnostics ({§turn-shape}). An omitted lifecycle declaration produces no diagnostic, and
1431
+ neither does the position of a present one ({§disposition-anywhere}). Source with no
1375
1432
  parsed operation yields `no valid Plurnk operation was found.` Targeted
1376
1433
  diagnostics are:
1377
1434
 
@@ -1381,9 +1438,7 @@ diagnostics are:
1381
1438
  its flags (`/(?i)shutdown|reactor/` is `/shutdown|reactor/i`; `^(?i)note:` is the
1382
1439
  anchored regex with `i`), with one warning-severity advisory naming the flag
1383
1440
  position after the closing `/`. Only a leading group of `i`, `m` and `s` lifts;
1384
- `(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched. From
1385
- the 2026-09-13 dumbox demo pass, where the model wrote the inline form, was
1386
- refused, and rewrote it as a trailing flag one turn later.
1441
+ `(?i:…)` scoped modifiers are valid ECMAScript and pass through untouched.
1387
1442
  - §regex-trailing-text A valid `/pattern/flags` prefix followed by horizontal
1388
1443
  whitespace and trailing text receives one concise trailing-content
1389
1444
  diagnostic, with or without flags, without assuming what the extra text was
@@ -1394,7 +1449,9 @@ diagnostics are:
1394
1449
  matcher, in whichever dialect its first characters claim
1395
1450
  ({§matcher-prefix-claims}): `/re/i`, `^anchored`, `//xpath`, `$.json`, `~words`,
1396
1451
  `&symbol`, or a sigil-less glob or literal such as `TODO` or `*.ts`. Those
1397
- operations take no body, so heading-line text can mean nothing else. On EDIT only
1452
+ matchers remain independent of owner options: a block without `pattern` never
1453
+ erases the heading matcher, and invalid blocks still reach the owning validator.
1454
+ These operations take no body, so heading-line text can mean nothing else. On EDIT only
1398
1455
  a sigil lifts, because plain heading-line text is the replacement body it always
1399
1456
  was; the lines beneath the heading are then the replacement, and none deletes each
1400
1457
  match ({§edit-pattern}). A trailing `<!-- aside -->` on the same line stays the
@@ -1404,8 +1461,7 @@ diagnostics are:
1404
1461
  `FIND (src/parser.ts) /\bparse\w+\b/` is the same operation as
1405
1462
  `FIND (src/parser.ts) [{"pattern": "/\\bparse\\w+\\b/"}]`. `^` claims the regex
1406
1463
  dialect without slashes or flags: the whole text is the pattern, so
1407
- `READ (reasoning:///1/1) ^NOTE:.*` selects a turn's note lines (operator,
1408
- 2026-09-12: "Recursive Reasoning").
1464
+ `READ (notes.md) ^Decision:.*` selects lines beginning with `Decision:`.
1409
1465
  - §trailing-slots **Slots after the matcher peel off the right.** The heading text after
1410
1466
  the matcher is read backwards: a trailing `<!-- aside -->`, a trailing `<scope>` in the
1411
1467
  shapes the lexer admits (result positions on FIND, text coordinates elsewhere) and a
@@ -1413,9 +1469,8 @@ diagnostics are:
1413
1469
  any order, each taken once and only when the heading did not already carry that slot,
1414
1470
  until what remains is the matcher. `READ (a.rs) /fn resolve_/ <1,-1> <!-- entities -->`
1415
1471
  is the same operation as `READ (a.rs) <1,-1> /fn resolve_/ <!-- entities -->`, with
1416
- one warning-severity advisory naming the canonical order for the scope or block
1417
- (operator, 2026-09-13: "swallow up anything that passes as legitimate plurnk"; the
1418
- 2026-09-13 dumbox run refused three headings for this in one turn). A matcher that
1472
+ one warning-severity advisory naming the canonical order for the scope or block:
1473
+ the grammar swallows up anything that passes as legitimate plurnk. A matcher that
1419
1474
  itself ends in one of those shapes takes the option escape.
1420
1475
  - §matcher-body-redirect **A body beneath those headings.** Text below the heading
1421
1476
  of a FIND, READ or KILL is a body, and those operations take none: the builder
@@ -1436,7 +1491,7 @@ diagnostics are:
1436
1491
  - §invalid-scope-diagnostic **Malformed scope content.** After a properly spaced
1437
1492
  scope opener, report the offending scope (at most 64 code points, ending at
1438
1493
  `>` or the heading's line end) and its operation's constraint: FIND result
1439
- positions, execution/TASK minutes, text coordinates, or no scope. Do not append advice for
1494
+ positions, execution minutes, text coordinates, or no scope. Do not append advice for
1440
1495
  other operations or infer why the producer supplied the value. Spacing and
1441
1496
  boundary-loss diagnostics retain their own contracts.
1442
1497
  - §misplaced-aside-advisory **Aside in the body.** A READ or FIND whose
@@ -1450,7 +1505,10 @@ diagnostics are:
1450
1505
  ({§matcher-body-redirect}).
1451
1506
 
1452
1507
  §error-shape The diagnostic class determines how much guidance the parser may
1453
- provide:
1508
+ provide. Advisories belong to one successfully built statement. A rejected
1509
+ statement emits its hard diagnostic, not normalization advisories; neither a
1510
+ rejection nor an internal exception carries advisories into another statement
1511
+ or parser invocation.
1454
1512
 
1455
1513
  | Class | Surface | Message contract |
1456
1514
  |------------------------|-----------------------|-------------------------------------------------------------------------------------------|
@@ -1472,7 +1530,7 @@ Examples of canonical hard facts:
1472
1530
  - `unrecognized character '<' in target`
1473
1531
  - `unexpected bracket modifier; the fence name selects the executor`
1474
1532
  - `unrecognized character 'X' in statement header`
1475
- - `TASK's body begins below the header`
1533
+ - `WAIT's body begins below the header`
1476
1534
  - `expected ')'; got ':'`
1477
1535
 
1478
1536
  Each malformed statement produces at most one hard error. The first recorded
@@ -1489,7 +1547,9 @@ without a closer is not such a case: it ends under {§closer-fallback}. `ParseRe
1489
1547
  before that point; recovered contexts and diagnostics at or beyond it are not
1490
1548
  public results. The tail is one separate boundary fact, not an additional
1491
1549
  malformed-statement diagnostic. Consumers must treat anything from that point
1492
- onward as undefined and must never dispatch a recovered statement from it.
1550
+ onward as undefined and must never dispatch a recovered statement from it; the
1551
+ facts before it are ordinary facts, and a consumer that runs them owes the
1552
+ author the tail's reason.
1493
1553
 
1494
1554
  | Consumer duty | Contract |
1495
1555
  |--------------------|----------------------------------------------------------------------------------------------------------------|
@@ -1508,6 +1568,6 @@ runtime constructs this; the parser provides the fields):
1508
1568
  "column": 12,
1509
1569
  "source": "parser",
1510
1570
  "severity": "error",
1511
- "message": "READ block opened at line 1 but was not closed with 3 backticks"
1571
+ "message": "READ block opened at line 1 but was not closed with 4 backticks"
1512
1572
  }
1513
1573
  ```