context-compiler 0.9.0.dev12__tar.gz → 0.9.0.dev13__tar.gz

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 (39) hide show
  1. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/PKG-INFO +99 -42
  2. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/README.md +98 -41
  3. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/01_llm_contradiction_error.py +4 -4
  4. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/06_llm_context_compaction.py +2 -2
  5. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/08_llm_replacement_precondition.py +4 -4
  6. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/README.md +6 -7
  7. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/common.py +8 -10
  8. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/run_demo.py +1 -2
  9. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/03_ambiguity_with_error.py +3 -3
  10. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/05_llm_integration_pattern.py +7 -7
  11. context_compiler-0.9.0.dev13/examples/08_apply_directive_decisions.py +95 -0
  12. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/README.md +10 -0
  13. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/_util.py +7 -5
  14. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/pyproject.toml +1 -1
  15. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/__init__.py +10 -13
  16. context_compiler-0.9.0.dev13/src/context_compiler/decision.py +107 -0
  17. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/engine.py +101 -45
  18. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/repl.py +49 -14
  19. context_compiler-0.9.0.dev12/demos/09_llm_confirmation_no_directive.py +0 -173
  20. context_compiler-0.9.0.dev12/src/context_compiler/decision_helpers.py +0 -30
  21. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/.gitignore +0 -0
  22. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/LICENSE +0 -0
  23. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/02_llm_constraint_guardrail.py +0 -0
  24. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/03_llm_premise_guardrail.py +0 -0
  25. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/04_llm_tool_denylist_guardrail.py +0 -0
  26. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/05_llm_prompt_drift_vs_state.py +0 -0
  27. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/07_llm_prompt_vs_state.py +0 -0
  28. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/__init__.py +0 -0
  29. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/demos/llm_client.py +0 -0
  30. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/01_persistent_guardrails.py +0 -0
  31. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/02_configuration_and_correction.py +0 -0
  32. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/04_tool_governance_denylist.py +0 -0
  33. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/06_step_sequence_and_state_restore.py +0 -0
  34. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/examples/07_single_policy_correction.py +0 -0
  35. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/host_support/__init__.py +0 -0
  36. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/host_support/provider_mode.py +0 -0
  37. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/const.py +0 -0
  38. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/grammar.py +0 -0
  39. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev13}/src/context_compiler/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: context-compiler
3
- Version: 0.9.0.dev12
3
+ Version: 0.9.0.dev13
4
4
  Summary: Deterministic conversational state engine for LLM applications.
5
5
  Project-URL: Homepage, https://github.com/rlippmann/context-compiler
6
6
  Project-URL: Repository, https://github.com/rlippmann/context-compiler
@@ -27,9 +27,8 @@ Description-Content-Type: text/markdown
27
27
  [![License](https://img.shields.io/pypi/l/context-compiler)](https://pypi.org/project/context-compiler/)
28
28
 
29
29
  Context Compiler is a deterministic conversational state authority for LLM applications.
30
- It handles canonical directive execution, semantic validation, deterministic
31
- error decisions, runtime semantic continuation boundaries, and structured authoritative state for
32
- the host.
30
+ It handles canonical directive execution, semantic validation, terminal error
31
+ decisions, advisory repairs, and structured authoritative state for the host.
33
32
 
34
33
  ## What Context Compiler provides
35
34
 
@@ -37,7 +36,7 @@ Context Compiler gives hosts fixed state rules:
37
36
 
38
37
  - handle canonical explicit state changes with deterministic rules
39
38
  - error instead of silent overwrite for blocked/ambiguous changes
40
- - preserve supported pending continuation when explicit confirmation is required
39
+ - return structured terminal errors with explicitly selectable advisory repairs
41
40
  - export and import authoritative state for host-managed persistence
42
41
  - produce structured authoritative state for downstream host decisions
43
42
 
@@ -84,8 +83,8 @@ The architecture has three layers:
84
83
  directive-shaped syntax, or ordinary no_directive
85
84
  - semantic evaluation decides whether a canonical directive updates state,
86
85
  clarifies, or no-ops
87
- - semantic continuation optionally preserves a deterministic blocked transition
88
- that needs explicit `yes` / `no`
86
+ - semantic evaluation returns an update or a terminal error with structured
87
+ advisory repairs when a canonical directive conflicts with state
89
88
 
90
89
  ### Explicit directive
91
90
 
@@ -103,7 +102,7 @@ use docker and prohibit peanuts
103
102
  ```
104
103
 
105
104
  - Without an authority layer: host/model behavior varies
106
- - Context Compiler: treats this as invalid directive-shaped syntax, keeps authoritative state unchanged, and does not create pending continuation
105
+ - Context Compiler: treats this as invalid directive-shaped syntax and keeps authoritative state unchanged
107
106
 
108
107
  ### State-dependent operation
109
108
 
@@ -159,8 +158,9 @@ Use Context Compiler in your host application first:
159
158
  ```python
160
159
  from context_compiler import (
161
160
  Engine,
162
- is_error,
163
- is_update,
161
+ NoDirectiveDecision,
162
+ SemanticErrorDecision,
163
+ UpdateDecision,
164
164
  )
165
165
 
166
166
  engine = Engine()
@@ -168,16 +168,16 @@ engine = Engine()
168
168
  user_input = "set premise current project uses uv"
169
169
  decision = engine.step(user_input)
170
170
 
171
- if is_error(decision):
172
- show_to_user(decision["message"])
173
- elif is_update(decision):
171
+ if isinstance(decision, SemanticErrorDecision):
172
+ show_to_user(decision.message)
173
+ elif isinstance(decision, UpdateDecision):
174
174
  messages = build_messages(
175
175
  premise=engine.premise,
176
176
  policies=engine.policies,
177
177
  user_input=user_input,
178
178
  )
179
179
  render(call_llm(messages))
180
- else:
180
+ elif isinstance(decision, NoDirectiveDecision):
181
181
  render(call_llm(user_input))
182
182
  ```
183
183
 
@@ -188,7 +188,7 @@ For runnable application-layer examples, see
188
188
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
189
189
  That companion repository shows enforcement points built on compiler state,
190
190
  including retrieval filtering, schema selection, tool gating, execution
191
- authorization, gateway middleware, runtime continuation handling, and prompt
191
+ authorization, gateway middleware, runtime state handling, and prompt
192
192
  construction.
193
193
 
194
194
  ## Does it Work?
@@ -235,6 +235,12 @@ for non-interactive usage.
235
235
  context-compiler --json < input.txt
236
236
  ```
237
237
 
238
+ The JSON output uses `output_version: 2`. Decision payloads expose structured
239
+ fields: updates include `changed`; semantic errors include `failure`, the
240
+ failed canonical `directive`, ordered advisory `repairs`, and `message`.
241
+ These fields are a CLI projection of ephemeral Decisions, not Decision object
242
+ serialization. Repairs are never applied automatically.
243
+
238
244
  Preload options load authoritative state:
239
245
 
240
246
  - `--initial-state-json` / `--initial-state-file` load saved state
@@ -268,7 +274,9 @@ uv run pytest
268
274
 
269
275
  ## Decision API
270
276
 
271
- Each user message produces a `Decision`.
277
+ Each user message produces one immutable `Decision` variant. Use concrete
278
+ variants with `isinstance` or pattern matching; use `kind` when generic code
279
+ needs the stable discriminator.
272
280
 
273
281
  ```python
274
282
  class DecisionKind(StrEnum):
@@ -276,26 +284,72 @@ class DecisionKind(StrEnum):
276
284
  UPDATE = "update"
277
285
  ERROR = "error"
278
286
 
279
- class Decision(TypedDict):
280
- kind: DecisionKind
281
- message: str | None
287
+ Decision = NoDirectiveDecision | UpdateDecision | SemanticErrorDecision
288
+
289
+ @dataclass(frozen=True, slots=True)
290
+ class NoDirectiveDecision:
291
+ kind = DecisionKind.NO_DIRECTIVE
292
+
293
+ @dataclass(frozen=True, slots=True)
294
+ class UpdateDecision:
295
+ kind = DecisionKind.UPDATE
296
+ changed: bool
297
+
298
+ @dataclass(frozen=True, slots=True)
299
+ class SemanticErrorDecision:
300
+ kind = DecisionKind.ERROR
301
+ failure: SemanticFailure
302
+ directive: CanonicalDirective
303
+ repairs: tuple[CanonicalDirective, ...]
304
+ message: str
282
305
  ```
283
306
 
284
- `message` is structurally present on every `Decision`, but it is semantically
285
- meaningful only for `error`. For `no_directive` and `update`, `message` is
286
- `None`.
307
+ `UpdateDecision.changed` reports whether authoritative state actually changed.
308
+ An accepted idempotent directive is still an `update` with
309
+ `changed=False`.
310
+
311
+ `SemanticErrorDecision.message` is derived human-readable text. Callers should
312
+ use `failure` for machine decisions rather than parsing the message.
313
+
314
+ `directive` is the canonical directive that failed semantic evaluation.
315
+ `repairs` is an ordered tuple of advisory canonical directives. Repairs are
316
+ never applied automatically; a host must explicitly submit a selected repair
317
+ through `engine.apply_directive(...)`.
318
+
319
+ The normative repair mapping is:
320
+
321
+ | Failure | Ordered advisory repairs |
322
+ | --- | --- |
323
+ | `PREMISE_ALREADY_SET` | `change premise to <requested value>` |
324
+ | `PREMISE_NOT_SET` | `set premise <requested value>` |
325
+ | `ITEM_PROHIBITED` | `remove policy <item>`; `use <item>` |
326
+ | `ITEM_ALREADY_IN_USE` | `remove policy <item>`; `prohibit <item>` |
327
+ | `REPLACEMENT_TARGET_PROHIBITED` | `remove policy <target>`; retry the original replacement directive |
328
+ | `REPLACEMENT_SOURCE_PROHIBITED` | no repair (`()`) |
329
+ | `REPLACEMENT_SOURCE_MISSING` | no repair (`()`) |
330
+
331
+ Repairs are canonical, ordered, and advisory only. The engine never applies
332
+ them automatically; hosts explicitly select and submit any repair they want to
333
+ use. `message` is presentation data, so control flow must use `failure` and
334
+ the structured directives rather than parse message text.
287
335
 
288
336
  Meaning:
289
337
 
290
338
  | kind | host behavior |
291
339
  | --- | --- |
292
340
  | no_directive | no canonical directive recognized; no authoritative state change; host decides what to do next |
293
- | update | authoritative state mutated; host may use updated state downstream |
294
- | error | show `message` and do not continue normal downstream processing yet |
341
+ | update | canonical directive was accepted; inspect `changed` and use updated state downstream |
342
+ | error | canonical directive was rejected semantically; inspect `failure`, show `message`, and optionally offer `repairs` |
343
+
344
+ `engine.step(...)` is the raw input boundary: it parses user input, may return
345
+ `NoDirectiveDecision` when no canonical directive is produced, and performs
346
+ semantic evaluation only after canonical parsing succeeds. The canonical
347
+ execution boundary is `engine.apply_directive(...)`, which accepts only a
348
+ `CanonicalDirective` and returns only `UpdateDecision` or
349
+ `SemanticErrorDecision`.
295
350
 
296
- For normal app code, prefer the exported decision helpers (`is_error`,
297
- `is_update`, `is_no_directive`, `get_error_message`)
298
- instead of direct key traversal.
351
+ Grammar failures do not produce semantic errors. A semantic error is possible
352
+ only after a canonical directive has parsed successfully.
299
353
 
300
354
  See [docs/api-reference.md](docs/api-reference.md) for the full public API
301
355
  reference.
@@ -305,8 +359,8 @@ Common API entry points:
305
359
  - engine lifecycle: `Engine()`, `engine.step(...)`,
306
360
  `engine.premise`, `engine.policies`, `engine.export_json(...)`,
307
361
  `engine.import_json(...)`
308
- - decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
309
- `get_error_message(...)`
362
+ - decision variants: `NoDirectiveDecision`, `UpdateDecision`,
363
+ `SemanticErrorDecision`, `SemanticFailure`
310
364
  - state transport: `engine.export_json(...)`, `engine.import_json(...)`
311
365
 
312
366
  ---
@@ -387,8 +441,8 @@ the compiler.
387
441
 
388
442
  - They transport **authoritative state only**
389
443
  - Hosts own any broader interaction or session workflow around that state
390
- - Pending continuation, if supported by the active engine contract, remains a
391
- runtime semantic concept rather than a documented persisted artifact
444
+ - Decision objects and advisory repairs are not persisted; persistence carries
445
+ authoritative state only
392
446
 
393
447
  ---
394
448
 
@@ -431,11 +485,14 @@ Directive-shaped invalid input is outside the canonical language, and
431
485
  `error` is reserved for canonical directives that later fail semantic
432
486
  evaluation against authoritative state.
433
487
 
434
- Pending continuation is a separate runtime layer. It may exist only after a
435
- canonical directive reaches a supported semantic `error` case. It never
436
- repairs malformed syntax or reinterprets non-canonical input as a directive.
437
- An absent source item in a canonical replacement directive is itself a
438
- semantic `error` case and does not authorize degradation to plain `use`.
488
+ A semantic error is a terminal result for the current input. It leaves
489
+ authoritative state unchanged and returns the failure classification, failed
490
+ canonical directive, ordered advisory repairs, and presentation message.
491
+ Repairs are canonical directives. They are advisory only, are never applied
492
+ automatically, and require explicit host selection and submission through
493
+ `engine.apply_directive(...)`. An absent source item in a canonical replacement
494
+ directive is a semantic `error` and does not authorize degradation to plain
495
+ `use`.
439
496
 
440
497
  Examples:
441
498
 
@@ -483,8 +540,8 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
483
540
  **Isn't this just prompt reinjection?**
484
541
  No. Prompt construction is one downstream use of authoritative state.
485
542
  Context Compiler is the authority layer that decides when state changes are
486
- allowed, when error is required, and how continuation state is
487
- restored. For runnable application-layer examples, see
543
+ allowed, when a terminal error is required, and which advisory repairs are
544
+ available. For runnable application-layer examples, see
488
545
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
489
546
 
490
547
  Human-facing interpretation is a separate concern. If you want to recognize
@@ -505,8 +562,8 @@ User: prohibit python_script
505
562
  ```
506
563
 
507
564
  Without an authority layer, the application must invent conflict-resolution and
508
- continuation rules itself. Context Compiler applies deterministic
509
- state-transition rules and can return error instead of silently
565
+ repair rules itself. Context Compiler applies deterministic state-transition
566
+ rules and can return a terminal error instead of silently
510
567
  overwriting state.
511
568
 
512
569
  ---
@@ -515,11 +572,11 @@ overwriting state.
515
572
 
516
573
  ### Guarantees
517
574
 
518
- - State changes only through explicit user directives or confirmation.
575
+ - State changes only through canonical directives that pass semantic evaluation.
519
576
  - Identical input sequences produce identical compiler state.
520
577
  - Model responses never modify compiler state.
521
578
  - Ambiguous directives trigger error instead of changing state.
522
- - Syntax errors never create pending continuation.
579
+ - Syntax errors never produce semantic errors or state changes.
523
580
 
524
581
  Behavioral tests and Hypothesis-based property tests verify these invariants.
525
582
 
@@ -5,9 +5,8 @@
5
5
  [![License](https://img.shields.io/pypi/l/context-compiler)](https://pypi.org/project/context-compiler/)
6
6
 
7
7
  Context Compiler is a deterministic conversational state authority for LLM applications.
8
- It handles canonical directive execution, semantic validation, deterministic
9
- error decisions, runtime semantic continuation boundaries, and structured authoritative state for
10
- the host.
8
+ It handles canonical directive execution, semantic validation, terminal error
9
+ decisions, advisory repairs, and structured authoritative state for the host.
11
10
 
12
11
  ## What Context Compiler provides
13
12
 
@@ -15,7 +14,7 @@ Context Compiler gives hosts fixed state rules:
15
14
 
16
15
  - handle canonical explicit state changes with deterministic rules
17
16
  - error instead of silent overwrite for blocked/ambiguous changes
18
- - preserve supported pending continuation when explicit confirmation is required
17
+ - return structured terminal errors with explicitly selectable advisory repairs
19
18
  - export and import authoritative state for host-managed persistence
20
19
  - produce structured authoritative state for downstream host decisions
21
20
 
@@ -62,8 +61,8 @@ The architecture has three layers:
62
61
  directive-shaped syntax, or ordinary no_directive
63
62
  - semantic evaluation decides whether a canonical directive updates state,
64
63
  clarifies, or no-ops
65
- - semantic continuation optionally preserves a deterministic blocked transition
66
- that needs explicit `yes` / `no`
64
+ - semantic evaluation returns an update or a terminal error with structured
65
+ advisory repairs when a canonical directive conflicts with state
67
66
 
68
67
  ### Explicit directive
69
68
 
@@ -81,7 +80,7 @@ use docker and prohibit peanuts
81
80
  ```
82
81
 
83
82
  - Without an authority layer: host/model behavior varies
84
- - Context Compiler: treats this as invalid directive-shaped syntax, keeps authoritative state unchanged, and does not create pending continuation
83
+ - Context Compiler: treats this as invalid directive-shaped syntax and keeps authoritative state unchanged
85
84
 
86
85
  ### State-dependent operation
87
86
 
@@ -137,8 +136,9 @@ Use Context Compiler in your host application first:
137
136
  ```python
138
137
  from context_compiler import (
139
138
  Engine,
140
- is_error,
141
- is_update,
139
+ NoDirectiveDecision,
140
+ SemanticErrorDecision,
141
+ UpdateDecision,
142
142
  )
143
143
 
144
144
  engine = Engine()
@@ -146,16 +146,16 @@ engine = Engine()
146
146
  user_input = "set premise current project uses uv"
147
147
  decision = engine.step(user_input)
148
148
 
149
- if is_error(decision):
150
- show_to_user(decision["message"])
151
- elif is_update(decision):
149
+ if isinstance(decision, SemanticErrorDecision):
150
+ show_to_user(decision.message)
151
+ elif isinstance(decision, UpdateDecision):
152
152
  messages = build_messages(
153
153
  premise=engine.premise,
154
154
  policies=engine.policies,
155
155
  user_input=user_input,
156
156
  )
157
157
  render(call_llm(messages))
158
- else:
158
+ elif isinstance(decision, NoDirectiveDecision):
159
159
  render(call_llm(user_input))
160
160
  ```
161
161
 
@@ -166,7 +166,7 @@ For runnable application-layer examples, see
166
166
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
167
167
  That companion repository shows enforcement points built on compiler state,
168
168
  including retrieval filtering, schema selection, tool gating, execution
169
- authorization, gateway middleware, runtime continuation handling, and prompt
169
+ authorization, gateway middleware, runtime state handling, and prompt
170
170
  construction.
171
171
 
172
172
  ## Does it Work?
@@ -213,6 +213,12 @@ for non-interactive usage.
213
213
  context-compiler --json < input.txt
214
214
  ```
215
215
 
216
+ The JSON output uses `output_version: 2`. Decision payloads expose structured
217
+ fields: updates include `changed`; semantic errors include `failure`, the
218
+ failed canonical `directive`, ordered advisory `repairs`, and `message`.
219
+ These fields are a CLI projection of ephemeral Decisions, not Decision object
220
+ serialization. Repairs are never applied automatically.
221
+
216
222
  Preload options load authoritative state:
217
223
 
218
224
  - `--initial-state-json` / `--initial-state-file` load saved state
@@ -246,7 +252,9 @@ uv run pytest
246
252
 
247
253
  ## Decision API
248
254
 
249
- Each user message produces a `Decision`.
255
+ Each user message produces one immutable `Decision` variant. Use concrete
256
+ variants with `isinstance` or pattern matching; use `kind` when generic code
257
+ needs the stable discriminator.
250
258
 
251
259
  ```python
252
260
  class DecisionKind(StrEnum):
@@ -254,26 +262,72 @@ class DecisionKind(StrEnum):
254
262
  UPDATE = "update"
255
263
  ERROR = "error"
256
264
 
257
- class Decision(TypedDict):
258
- kind: DecisionKind
259
- message: str | None
265
+ Decision = NoDirectiveDecision | UpdateDecision | SemanticErrorDecision
266
+
267
+ @dataclass(frozen=True, slots=True)
268
+ class NoDirectiveDecision:
269
+ kind = DecisionKind.NO_DIRECTIVE
270
+
271
+ @dataclass(frozen=True, slots=True)
272
+ class UpdateDecision:
273
+ kind = DecisionKind.UPDATE
274
+ changed: bool
275
+
276
+ @dataclass(frozen=True, slots=True)
277
+ class SemanticErrorDecision:
278
+ kind = DecisionKind.ERROR
279
+ failure: SemanticFailure
280
+ directive: CanonicalDirective
281
+ repairs: tuple[CanonicalDirective, ...]
282
+ message: str
260
283
  ```
261
284
 
262
- `message` is structurally present on every `Decision`, but it is semantically
263
- meaningful only for `error`. For `no_directive` and `update`, `message` is
264
- `None`.
285
+ `UpdateDecision.changed` reports whether authoritative state actually changed.
286
+ An accepted idempotent directive is still an `update` with
287
+ `changed=False`.
288
+
289
+ `SemanticErrorDecision.message` is derived human-readable text. Callers should
290
+ use `failure` for machine decisions rather than parsing the message.
291
+
292
+ `directive` is the canonical directive that failed semantic evaluation.
293
+ `repairs` is an ordered tuple of advisory canonical directives. Repairs are
294
+ never applied automatically; a host must explicitly submit a selected repair
295
+ through `engine.apply_directive(...)`.
296
+
297
+ The normative repair mapping is:
298
+
299
+ | Failure | Ordered advisory repairs |
300
+ | --- | --- |
301
+ | `PREMISE_ALREADY_SET` | `change premise to <requested value>` |
302
+ | `PREMISE_NOT_SET` | `set premise <requested value>` |
303
+ | `ITEM_PROHIBITED` | `remove policy <item>`; `use <item>` |
304
+ | `ITEM_ALREADY_IN_USE` | `remove policy <item>`; `prohibit <item>` |
305
+ | `REPLACEMENT_TARGET_PROHIBITED` | `remove policy <target>`; retry the original replacement directive |
306
+ | `REPLACEMENT_SOURCE_PROHIBITED` | no repair (`()`) |
307
+ | `REPLACEMENT_SOURCE_MISSING` | no repair (`()`) |
308
+
309
+ Repairs are canonical, ordered, and advisory only. The engine never applies
310
+ them automatically; hosts explicitly select and submit any repair they want to
311
+ use. `message` is presentation data, so control flow must use `failure` and
312
+ the structured directives rather than parse message text.
265
313
 
266
314
  Meaning:
267
315
 
268
316
  | kind | host behavior |
269
317
  | --- | --- |
270
318
  | no_directive | no canonical directive recognized; no authoritative state change; host decides what to do next |
271
- | update | authoritative state mutated; host may use updated state downstream |
272
- | error | show `message` and do not continue normal downstream processing yet |
319
+ | update | canonical directive was accepted; inspect `changed` and use updated state downstream |
320
+ | error | canonical directive was rejected semantically; inspect `failure`, show `message`, and optionally offer `repairs` |
321
+
322
+ `engine.step(...)` is the raw input boundary: it parses user input, may return
323
+ `NoDirectiveDecision` when no canonical directive is produced, and performs
324
+ semantic evaluation only after canonical parsing succeeds. The canonical
325
+ execution boundary is `engine.apply_directive(...)`, which accepts only a
326
+ `CanonicalDirective` and returns only `UpdateDecision` or
327
+ `SemanticErrorDecision`.
273
328
 
274
- For normal app code, prefer the exported decision helpers (`is_error`,
275
- `is_update`, `is_no_directive`, `get_error_message`)
276
- instead of direct key traversal.
329
+ Grammar failures do not produce semantic errors. A semantic error is possible
330
+ only after a canonical directive has parsed successfully.
277
331
 
278
332
  See [docs/api-reference.md](docs/api-reference.md) for the full public API
279
333
  reference.
@@ -283,8 +337,8 @@ Common API entry points:
283
337
  - engine lifecycle: `Engine()`, `engine.step(...)`,
284
338
  `engine.premise`, `engine.policies`, `engine.export_json(...)`,
285
339
  `engine.import_json(...)`
286
- - decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
287
- `get_error_message(...)`
340
+ - decision variants: `NoDirectiveDecision`, `UpdateDecision`,
341
+ `SemanticErrorDecision`, `SemanticFailure`
288
342
  - state transport: `engine.export_json(...)`, `engine.import_json(...)`
289
343
 
290
344
  ---
@@ -365,8 +419,8 @@ the compiler.
365
419
 
366
420
  - They transport **authoritative state only**
367
421
  - Hosts own any broader interaction or session workflow around that state
368
- - Pending continuation, if supported by the active engine contract, remains a
369
- runtime semantic concept rather than a documented persisted artifact
422
+ - Decision objects and advisory repairs are not persisted; persistence carries
423
+ authoritative state only
370
424
 
371
425
  ---
372
426
 
@@ -409,11 +463,14 @@ Directive-shaped invalid input is outside the canonical language, and
409
463
  `error` is reserved for canonical directives that later fail semantic
410
464
  evaluation against authoritative state.
411
465
 
412
- Pending continuation is a separate runtime layer. It may exist only after a
413
- canonical directive reaches a supported semantic `error` case. It never
414
- repairs malformed syntax or reinterprets non-canonical input as a directive.
415
- An absent source item in a canonical replacement directive is itself a
416
- semantic `error` case and does not authorize degradation to plain `use`.
466
+ A semantic error is a terminal result for the current input. It leaves
467
+ authoritative state unchanged and returns the failure classification, failed
468
+ canonical directive, ordered advisory repairs, and presentation message.
469
+ Repairs are canonical directives. They are advisory only, are never applied
470
+ automatically, and require explicit host selection and submission through
471
+ `engine.apply_directive(...)`. An absent source item in a canonical replacement
472
+ directive is a semantic `error` and does not authorize degradation to plain
473
+ `use`.
417
474
 
418
475
  Examples:
419
476
 
@@ -461,8 +518,8 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
461
518
  **Isn't this just prompt reinjection?**
462
519
  No. Prompt construction is one downstream use of authoritative state.
463
520
  Context Compiler is the authority layer that decides when state changes are
464
- allowed, when error is required, and how continuation state is
465
- restored. For runnable application-layer examples, see
521
+ allowed, when a terminal error is required, and which advisory repairs are
522
+ available. For runnable application-layer examples, see
466
523
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
467
524
 
468
525
  Human-facing interpretation is a separate concern. If you want to recognize
@@ -483,8 +540,8 @@ User: prohibit python_script
483
540
  ```
484
541
 
485
542
  Without an authority layer, the application must invent conflict-resolution and
486
- continuation rules itself. Context Compiler applies deterministic
487
- state-transition rules and can return error instead of silently
543
+ repair rules itself. Context Compiler applies deterministic state-transition
544
+ rules and can return a terminal error instead of silently
488
545
  overwriting state.
489
546
 
490
547
  ---
@@ -493,11 +550,11 @@ overwriting state.
493
550
 
494
551
  ### Guarantees
495
552
 
496
- - State changes only through explicit user directives or confirmation.
553
+ - State changes only through canonical directives that pass semantic evaluation.
497
554
  - Identical input sequences produce identical compiler state.
498
555
  - Model responses never modify compiler state.
499
556
  - Ambiguous directives trigger error instead of changing state.
500
- - Syntax errors never create pending continuation.
557
+ - Syntax errors never produce semantic errors or state changes.
501
558
 
502
559
  Behavioral tests and Hypothesis-based property tests verify these invariants.
503
560
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from context_compiler import (
4
4
  Engine,
5
- is_error,
5
+ SemanticErrorDecision,
6
6
  )
7
7
  from demos.common import (
8
8
  build_baseline_messages,
@@ -69,9 +69,9 @@ def main() -> None:
69
69
  reinjected_output = complete_messages(reinjected_messages)
70
70
  print_model_output("Reinjected-state", reinjected_output)
71
71
 
72
- if is_error(second):
72
+ if isinstance(second, SemanticErrorDecision):
73
73
  print_messages("compiler-mediated (full)", [])
74
- mediated_output = f"[no call] error required: {second['message']}\nACTION:error"
74
+ mediated_output = f"[no call] error required: {second.message}\nACTION:error"
75
75
  print_model_output("Compiler-mediated (full)", mediated_output)
76
76
  else:
77
77
  premise, policies = observe_engine(engine)
@@ -106,7 +106,7 @@ def main() -> None:
106
106
  compact_action = extract_tag_value(compact_output, "ACTION")
107
107
  baseline_respects = baseline_action is not None and baseline_action.lower() == "error"
108
108
  reinjected_respects = reinjected_action is not None and reinjected_action.lower() == "error"
109
- compiler_host_blocked = is_error(second)
109
+ compiler_host_blocked = isinstance(second, SemanticErrorDecision)
110
110
  mediated_respects = compiler_host_blocked
111
111
  compact_respects = compacted_prompt is not None or (
112
112
  compact_action is not None and compact_action.lower() == "error"
@@ -1,8 +1,8 @@
1
1
  """Demo 6: host-side prompt replacement from authoritative step-derived state."""
2
2
 
3
3
  from context_compiler import (
4
- DECISION_UPDATE,
5
4
  Engine,
5
+ UpdateDecision,
6
6
  )
7
7
  from demos.common import compact_user_turns, is_verbose, print_info_report, state_observations
8
8
 
@@ -46,7 +46,7 @@ def _compile_premise(turns: list[str]) -> str:
46
46
  engine = Engine()
47
47
  for turn in turns:
48
48
  decision = engine.step(turn)
49
- assert decision["kind"] == DECISION_UPDATE
49
+ assert isinstance(decision, UpdateDecision)
50
50
  compiled_premise = engine.premise
51
51
  assert compiled_premise is not None
52
52
  return compiled_premise
@@ -4,7 +4,7 @@ from collections.abc import Mapping
4
4
 
5
5
  from context_compiler import (
6
6
  Engine,
7
- is_error,
7
+ SemanticErrorDecision,
8
8
  )
9
9
  from demos.common import (
10
10
  build_baseline_messages,
@@ -69,7 +69,7 @@ def main() -> None:
69
69
  reinjected_output = complete_messages(reinjected_messages)
70
70
  print_model_output("Reinjected-state", reinjected_output)
71
71
 
72
- if is_error(decision):
72
+ if isinstance(decision, SemanticErrorDecision):
73
73
  print_messages("compiler-mediated (full)", [])
74
74
  mediated_output = "[no call] authoritative state blocked replacement without source use"
75
75
  print_model_output("Compiler-mediated (full)", mediated_output)
@@ -99,7 +99,7 @@ def main() -> None:
99
99
 
100
100
  baseline_has_authoritative_precondition = False
101
101
  reinjected_has_authoritative_precondition = False
102
- compiler_pass = is_error(decision) and state_preserved
102
+ compiler_pass = isinstance(decision, SemanticErrorDecision) and state_preserved
103
103
  compact_pass = compact_error_preserved and compact_state_preserved
104
104
 
105
105
  print_host_check(
@@ -114,7 +114,7 @@ def main() -> None:
114
114
  )
115
115
  print_host_check(
116
116
  "COMPILER_BLOCKED_INVALID_REPLACEMENT",
117
- yes_no(is_error(decision)),
117
+ yes_no(isinstance(decision, SemanticErrorDecision)),
118
118
  context="compiler-mediated",
119
119
  )
120
120
  print_host_check(
@@ -35,8 +35,7 @@ Runnable application-layer enforcement-point integrations live in
35
35
  | [05](./05_llm_prompt_drift_vs_state.py) | Prompt drift | long transcript failure | weaker long-context models ([see Demo 05 example](#demo-05-example-prompt-drift-under-longer-context)) |
36
36
  | [06](./06_llm_context_compaction.py) | Context compaction | saved compiler state replacing transcript context | small or local models |
37
37
  | [07](./07_llm_prompt_vs_state.py) | Prompt engineering comparison | prompting vs saved compiler state | any model with long transcript sensitivity |
38
- | [08](./08_llm_replacement_precondition.py) | Replacement precondition | missing-source replacement applies deterministically from authoritative state | any model |
39
- | [09](./09_llm_confirmation_no_directive.py) | Confirmation no_directive | invalid replacement does not create an engine-owned confirmation state; later confirmation-style input remains ordinary no_directive | any model |
38
+ | [08](./08_llm_replacement_precondition.py) | Replacement precondition | missing-source replacement is terminal; state remains unchanged | any model |
40
39
 
41
40
  Stronger frontier models may show these behaviors less often, but the same
42
41
  patterns still appear in real applications.
@@ -167,18 +166,18 @@ includes:
167
166
 
168
167
  Notes:
169
168
 
170
- - There are **8 scored demos** (`01`–`05`, `07`, `08`, `09`). `06_context_compaction` is informational and excluded from PASS/FAIL totals.
169
+ - There are **7 scored demos** (`01`–`05`, `07`, `08`). `06_context_compaction` is informational and excluded from PASS/FAIL totals.
171
170
  - Anthropic runs in this repo are executed through the `openai_compatible` provider path.
172
171
  - `PASS` means the demo-specific expected-behavior check for that path succeeded; `FAIL` means it did not.
173
172
  - `reinjected-state` can be enough for some persistence cases; in this demo set it is intentionally used as a prompt-only comparison baseline.
174
- - Scored checks focus on app-side authority rules (for example blocked mutation and non-pending invalid replacement handling), not model prose quality. `reinjected-state` remains plain text injection only.
173
+ - Scored checks focus on app-side authority rules (for example blocked mutation and terminal invalid replacement handling), not model prose quality. `reinjected-state` remains plain text injection only.
175
174
  - Interpretation:
176
175
 
177
176
  - Demos `01`-`05` and `07` mostly test persistence and policy-following behavior across turns.
178
- - Demos `08`/`09` test rules for when state is allowed to change.
179
- - Demos `08` and `09` cover authority semantics prompt text does not implement by itself, such as replacement preconditions, blocked mutations, and waiting for confirmation before saving changes.
177
+ - Demo `08` tests rules for when state is allowed to change.
178
+ - Demo `08` covers authority semantics prompt text does not implement by itself, such as replacement preconditions, blocked mutations, and terminal semantic errors before saving changes.
180
179
  - Plain prompt reinjection can produce reasonable answers, but it does not run these authority checks by itself and is not the only or preferred production integration pattern.
181
- - Similar outcomes across models in `08`/`09` reflect app behavior limits, not model leaderboard ranking.
180
+ - Similar outcomes across models in `08` reflect app behavior limits, not model leaderboard ranking.
182
181
 
183
182
  ### Demo 05 example (prompt drift under longer context)
184
183