context-compiler 0.9.0.dev12__tar.gz → 0.9.0.dev14__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.dev14}/PKG-INFO +118 -48
  2. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/README.md +117 -47
  3. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/01_llm_contradiction_error.py +4 -4
  4. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/06_llm_context_compaction.py +2 -2
  5. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/08_llm_replacement_precondition.py +4 -4
  6. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/README.md +6 -7
  7. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/common.py +8 -10
  8. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/run_demo.py +1 -2
  9. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/03_ambiguity_with_error.py +3 -3
  10. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/05_llm_integration_pattern.py +7 -7
  11. context_compiler-0.9.0.dev14/examples/08_apply_directive_decisions.py +95 -0
  12. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/README.md +10 -0
  13. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/_util.py +7 -5
  14. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/pyproject.toml +1 -1
  15. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/src/context_compiler/__init__.py +10 -13
  16. context_compiler-0.9.0.dev14/src/context_compiler/decision.py +107 -0
  17. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/src/context_compiler/engine.py +101 -45
  18. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/src/context_compiler/grammar.py +31 -3
  19. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/src/context_compiler/repl.py +49 -14
  20. context_compiler-0.9.0.dev12/demos/09_llm_confirmation_no_directive.py +0 -173
  21. context_compiler-0.9.0.dev12/src/context_compiler/decision_helpers.py +0 -30
  22. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/.gitignore +0 -0
  23. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/LICENSE +0 -0
  24. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/02_llm_constraint_guardrail.py +0 -0
  25. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/03_llm_premise_guardrail.py +0 -0
  26. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/04_llm_tool_denylist_guardrail.py +0 -0
  27. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/05_llm_prompt_drift_vs_state.py +0 -0
  28. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/07_llm_prompt_vs_state.py +0 -0
  29. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/__init__.py +0 -0
  30. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/demos/llm_client.py +0 -0
  31. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/01_persistent_guardrails.py +0 -0
  32. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/02_configuration_and_correction.py +0 -0
  33. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/04_tool_governance_denylist.py +0 -0
  34. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/06_step_sequence_and_state_restore.py +0 -0
  35. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/examples/07_single_policy_correction.py +0 -0
  36. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/host_support/__init__.py +0 -0
  37. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/host_support/provider_mode.py +0 -0
  38. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/src/context_compiler/const.py +0 -0
  39. {context_compiler-0.9.0.dev12 → context_compiler-0.9.0.dev14}/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.dev14
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
@@ -25,11 +25,11 @@ Description-Content-Type: text/markdown
25
25
  [![PyPI version](https://img.shields.io/pypi/v/context-compiler)](https://pypi.org/project/context-compiler/)
26
26
  [![Python versions](https://img.shields.io/pypi/pyversions/context-compiler)](https://pypi.org/project/context-compiler/)
27
27
  [![License](https://img.shields.io/pypi/l/context-compiler)](https://pypi.org/project/context-compiler/)
28
+ [![codecov](https://codecov.io/gh/rlippmann/context-compiler/branch/main/graph/badge.svg)](https://codecov.io/gh/rlippmann/context-compiler)
28
29
 
29
30
  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.
31
+ It handles canonical directive execution, semantic validation, terminal error
32
+ decisions, advisory repairs, and structured authoritative state for the host.
33
33
 
34
34
  ## What Context Compiler provides
35
35
 
@@ -37,7 +37,7 @@ Context Compiler gives hosts fixed state rules:
37
37
 
38
38
  - handle canonical explicit state changes with deterministic rules
39
39
  - error instead of silent overwrite for blocked/ambiguous changes
40
- - preserve supported pending continuation when explicit confirmation is required
40
+ - return structured terminal errors with explicitly selectable advisory repairs
41
41
  - export and import authoritative state for host-managed persistence
42
42
  - produce structured authoritative state for downstream host decisions
43
43
 
@@ -84,8 +84,8 @@ The architecture has three layers:
84
84
  directive-shaped syntax, or ordinary no_directive
85
85
  - semantic evaluation decides whether a canonical directive updates state,
86
86
  clarifies, or no-ops
87
- - semantic continuation optionally preserves a deterministic blocked transition
88
- that needs explicit `yes` / `no`
87
+ - semantic evaluation returns an update or a terminal error with structured
88
+ advisory repairs when a canonical directive conflicts with state
89
89
 
90
90
  ### Explicit directive
91
91
 
@@ -96,14 +96,14 @@ set premise concise replies
96
96
  - Base model: silently accepts / rewrites
97
97
  - Context Compiler: applies a repeatable state update
98
98
 
99
- ### Single-directive grammar
99
+ ### Compound directive rejection
100
100
 
101
101
  ```text
102
102
  use docker and prohibit peanuts
103
103
  ```
104
104
 
105
105
  - 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
106
+ - Context Compiler: treats this as invalid directive-shaped syntax and keeps authoritative state unchanged
107
107
 
108
108
  ### State-dependent operation
109
109
 
@@ -114,7 +114,7 @@ use podman instead of docker
114
114
 
115
115
  - Without explicit state transition rules: behavior depends on host/model handling
116
116
  - Context Compiler: applies the deterministic resulting transition when
117
- `docker` is absent and `use podman` is otherwise valid; other semantic
117
+ `docker` is present and `use podman` is otherwise valid; other semantic
118
118
  conflicts may still error
119
119
 
120
120
  ### Lifecycle enforcement
@@ -159,8 +159,9 @@ Use Context Compiler in your host application first:
159
159
  ```python
160
160
  from context_compiler import (
161
161
  Engine,
162
- is_error,
163
- is_update,
162
+ NoDirectiveDecision,
163
+ SemanticErrorDecision,
164
+ UpdateDecision,
164
165
  )
165
166
 
166
167
  engine = Engine()
@@ -168,16 +169,16 @@ engine = Engine()
168
169
  user_input = "set premise current project uses uv"
169
170
  decision = engine.step(user_input)
170
171
 
171
- if is_error(decision):
172
- show_to_user(decision["message"])
173
- elif is_update(decision):
172
+ if isinstance(decision, SemanticErrorDecision):
173
+ show_to_user(decision.message)
174
+ elif isinstance(decision, UpdateDecision):
174
175
  messages = build_messages(
175
176
  premise=engine.premise,
176
177
  policies=engine.policies,
177
178
  user_input=user_input,
178
179
  )
179
180
  render(call_llm(messages))
180
- else:
181
+ elif isinstance(decision, NoDirectiveDecision):
181
182
  render(call_llm(user_input))
182
183
  ```
183
184
 
@@ -188,7 +189,7 @@ For runnable application-layer examples, see
188
189
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
189
190
  That companion repository shows enforcement points built on compiler state,
190
191
  including retrieval filtering, schema selection, tool gating, execution
191
- authorization, gateway middleware, runtime continuation handling, and prompt
192
+ authorization, gateway middleware, runtime state handling, and prompt
192
193
  construction.
193
194
 
194
195
  ## Does it Work?
@@ -235,6 +236,12 @@ for non-interactive usage.
235
236
  context-compiler --json < input.txt
236
237
  ```
237
238
 
239
+ The JSON output uses `output_version: 2`. Decision payloads expose structured
240
+ fields: updates include `changed`; semantic errors include `failure`, the
241
+ failed canonical `directive`, ordered advisory `repairs`, and `message`.
242
+ These fields are a CLI projection of ephemeral Decisions, not Decision object
243
+ serialization. Repairs are never applied automatically.
244
+
238
245
  Preload options load authoritative state:
239
246
 
240
247
  - `--initial-state-json` / `--initial-state-file` load saved state
@@ -266,9 +273,15 @@ uv sync --dev
266
273
  uv run pytest
267
274
  ```
268
275
 
276
+ CI enforces 100% coverage for the core `src/context_compiler` package. The
277
+ coverage badge represents that authoritative core-package target, not the
278
+ entire repository.
279
+
269
280
  ## Decision API
270
281
 
271
- Each user message produces a `Decision`.
282
+ Each user message produces one immutable `Decision` variant. Use concrete
283
+ variants with `isinstance` or pattern matching; use `kind` when generic code
284
+ needs the stable discriminator.
272
285
 
273
286
  ```python
274
287
  class DecisionKind(StrEnum):
@@ -276,26 +289,72 @@ class DecisionKind(StrEnum):
276
289
  UPDATE = "update"
277
290
  ERROR = "error"
278
291
 
279
- class Decision(TypedDict):
280
- kind: DecisionKind
281
- message: str | None
292
+ Decision = NoDirectiveDecision | UpdateDecision | SemanticErrorDecision
293
+
294
+ @dataclass(frozen=True, slots=True)
295
+ class NoDirectiveDecision:
296
+ kind = DecisionKind.NO_DIRECTIVE
297
+
298
+ @dataclass(frozen=True, slots=True)
299
+ class UpdateDecision:
300
+ kind = DecisionKind.UPDATE
301
+ changed: bool
302
+
303
+ @dataclass(frozen=True, slots=True)
304
+ class SemanticErrorDecision:
305
+ kind = DecisionKind.ERROR
306
+ failure: SemanticFailure
307
+ directive: CanonicalDirective
308
+ repairs: tuple[CanonicalDirective, ...]
309
+ message: str
282
310
  ```
283
311
 
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`.
312
+ `UpdateDecision.changed` reports whether authoritative state actually changed.
313
+ An accepted idempotent directive is still an `update` with
314
+ `changed=False`.
315
+
316
+ `SemanticErrorDecision.message` is derived human-readable text. Callers should
317
+ use `failure` for machine decisions rather than parsing the message.
318
+
319
+ `directive` is the canonical directive that failed semantic evaluation.
320
+ `repairs` is an ordered tuple of advisory canonical directives. Repairs are
321
+ never applied automatically; a host must explicitly submit a selected repair
322
+ through `engine.apply_directive(...)`.
323
+
324
+ The normative repair mapping is:
325
+
326
+ | Failure | Ordered advisory repairs |
327
+ | --- | --- |
328
+ | `PREMISE_ALREADY_SET` | `change premise to <requested value>` |
329
+ | `PREMISE_NOT_SET` | `set premise <requested value>` |
330
+ | `ITEM_PROHIBITED` | `remove policy <item>`; `use <item>` |
331
+ | `ITEM_ALREADY_IN_USE` | `remove policy <item>`; `prohibit <item>` |
332
+ | `REPLACEMENT_TARGET_PROHIBITED` | `remove policy <target>`; retry the original replacement directive |
333
+ | `REPLACEMENT_SOURCE_PROHIBITED` | no repair (`()`) |
334
+ | `REPLACEMENT_SOURCE_MISSING` | no repair (`()`) |
335
+
336
+ Repairs are canonical, ordered, and advisory only. The engine never applies
337
+ them automatically; hosts explicitly select and submit any repair they want to
338
+ use. `message` is presentation data, so control flow must use `failure` and
339
+ the structured directives rather than parse message text.
287
340
 
288
341
  Meaning:
289
342
 
290
343
  | kind | host behavior |
291
344
  | --- | --- |
292
345
  | 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 |
346
+ | update | canonical directive was accepted; inspect `changed` and use updated state downstream |
347
+ | error | canonical directive was rejected semantically; inspect `failure`, show `message`, and optionally offer `repairs` |
348
+
349
+ `engine.step(...)` is the raw input boundary: it parses user input, may return
350
+ `NoDirectiveDecision` when no canonical directive is produced, and performs
351
+ semantic evaluation only after canonical parsing succeeds. The canonical
352
+ execution boundary is `engine.apply_directive(...)`, which accepts only a
353
+ `CanonicalDirective` and returns only `UpdateDecision` or
354
+ `SemanticErrorDecision`.
295
355
 
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.
356
+ Grammar failures do not produce semantic errors. A semantic error is possible
357
+ only after a canonical directive has parsed successfully.
299
358
 
300
359
  See [docs/api-reference.md](docs/api-reference.md) for the full public API
301
360
  reference.
@@ -305,8 +364,8 @@ Common API entry points:
305
364
  - engine lifecycle: `Engine()`, `engine.step(...)`,
306
365
  `engine.premise`, `engine.policies`, `engine.export_json(...)`,
307
366
  `engine.import_json(...)`
308
- - decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
309
- `get_error_message(...)`
367
+ - decision variants: `NoDirectiveDecision`, `UpdateDecision`,
368
+ `SemanticErrorDecision`, `SemanticFailure`
310
369
  - state transport: `engine.export_json(...)`, `engine.import_json(...)`
311
370
 
312
371
  ---
@@ -387,8 +446,8 @@ the compiler.
387
446
 
388
447
  - They transport **authoritative state only**
389
448
  - 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
449
+ - Decision objects and advisory repairs are not persisted; persistence carries
450
+ authoritative state only
392
451
 
393
452
  ---
394
453
 
@@ -426,16 +485,20 @@ User: reset policies
426
485
  User: clear state
427
486
  ```
428
487
 
429
- Grammar invariant: one input may contain at most one canonical directive.
488
+ Grammar invariant: a single input never applies more than one canonical
489
+ directive.
430
490
  Directive-shaped invalid input is outside the canonical language, and
431
491
  `error` is reserved for canonical directives that later fail semantic
432
492
  evaluation against authoritative state.
433
493
 
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`.
494
+ A semantic error is a terminal result for the current input. It leaves
495
+ authoritative state unchanged and returns the failure classification, failed
496
+ canonical directive, ordered advisory repairs, and presentation message.
497
+ Repairs are canonical directives. They are advisory only, are never applied
498
+ automatically, and require explicit host selection and submission through
499
+ `engine.apply_directive(...)`. An absent source item in a canonical replacement
500
+ directive is a semantic `error` and does not authorize degradation to plain
501
+ `use`.
439
502
 
440
503
  Examples:
441
504
 
@@ -447,18 +510,25 @@ clear state
447
510
 
448
511
  Invalid:
449
512
  use docker and prohibit peanuts
450
- set premise vegetarian and use docker
451
513
  clear state then set premise new project
514
+
515
+ Premise payload (opaque):
516
+ set premise vegetarian and use docker
452
517
  ```
453
518
 
519
+ Policy compounds such as `use docker and prohibit peanuts` remain invalid;
520
+ premise `VALUE` is opaque and may contain directive-like words.
521
+
454
522
  Quote behavior follows the current grammar literally:
455
523
 
456
524
  ```text
457
- Passthrough:
525
+ Passthrough (`no_directive`):
458
526
  "use docker and prohibit peanuts"
459
527
 
460
- Invalid:
528
+ Invalid directive:
461
529
  use "docker and prohibit peanuts"
530
+
531
+ Canonical directive (`set premise`):
462
532
  set premise "use docker and prohibit peanuts"
463
533
  ```
464
534
 
@@ -483,8 +553,8 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
483
553
  **Isn't this just prompt reinjection?**
484
554
  No. Prompt construction is one downstream use of authoritative state.
485
555
  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
556
+ allowed, when a terminal error is required, and which advisory repairs are
557
+ available. For runnable application-layer examples, see
488
558
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
489
559
 
490
560
  Human-facing interpretation is a separate concern. If you want to recognize
@@ -505,8 +575,8 @@ User: prohibit python_script
505
575
  ```
506
576
 
507
577
  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
578
+ repair rules itself. Context Compiler applies deterministic state-transition
579
+ rules and can return a terminal error instead of silently
510
580
  overwriting state.
511
581
 
512
582
  ---
@@ -515,11 +585,11 @@ overwriting state.
515
585
 
516
586
  ### Guarantees
517
587
 
518
- - State changes only through explicit user directives or confirmation.
588
+ - State changes only through canonical directives that pass semantic evaluation.
519
589
  - Identical input sequences produce identical compiler state.
520
590
  - Model responses never modify compiler state.
521
591
  - Ambiguous directives trigger error instead of changing state.
522
- - Syntax errors never create pending continuation.
592
+ - Syntax errors never produce semantic errors or state changes.
523
593
 
524
594
  Behavioral tests and Hypothesis-based property tests verify these invariants.
525
595
 
@@ -3,11 +3,11 @@
3
3
  [![PyPI version](https://img.shields.io/pypi/v/context-compiler)](https://pypi.org/project/context-compiler/)
4
4
  [![Python versions](https://img.shields.io/pypi/pyversions/context-compiler)](https://pypi.org/project/context-compiler/)
5
5
  [![License](https://img.shields.io/pypi/l/context-compiler)](https://pypi.org/project/context-compiler/)
6
+ [![codecov](https://codecov.io/gh/rlippmann/context-compiler/branch/main/graph/badge.svg)](https://codecov.io/gh/rlippmann/context-compiler)
6
7
 
7
8
  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.
9
+ It handles canonical directive execution, semantic validation, terminal error
10
+ decisions, advisory repairs, and structured authoritative state for the host.
11
11
 
12
12
  ## What Context Compiler provides
13
13
 
@@ -15,7 +15,7 @@ Context Compiler gives hosts fixed state rules:
15
15
 
16
16
  - handle canonical explicit state changes with deterministic rules
17
17
  - error instead of silent overwrite for blocked/ambiguous changes
18
- - preserve supported pending continuation when explicit confirmation is required
18
+ - return structured terminal errors with explicitly selectable advisory repairs
19
19
  - export and import authoritative state for host-managed persistence
20
20
  - produce structured authoritative state for downstream host decisions
21
21
 
@@ -62,8 +62,8 @@ The architecture has three layers:
62
62
  directive-shaped syntax, or ordinary no_directive
63
63
  - semantic evaluation decides whether a canonical directive updates state,
64
64
  clarifies, or no-ops
65
- - semantic continuation optionally preserves a deterministic blocked transition
66
- that needs explicit `yes` / `no`
65
+ - semantic evaluation returns an update or a terminal error with structured
66
+ advisory repairs when a canonical directive conflicts with state
67
67
 
68
68
  ### Explicit directive
69
69
 
@@ -74,14 +74,14 @@ set premise concise replies
74
74
  - Base model: silently accepts / rewrites
75
75
  - Context Compiler: applies a repeatable state update
76
76
 
77
- ### Single-directive grammar
77
+ ### Compound directive rejection
78
78
 
79
79
  ```text
80
80
  use docker and prohibit peanuts
81
81
  ```
82
82
 
83
83
  - 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
84
+ - Context Compiler: treats this as invalid directive-shaped syntax and keeps authoritative state unchanged
85
85
 
86
86
  ### State-dependent operation
87
87
 
@@ -92,7 +92,7 @@ use podman instead of docker
92
92
 
93
93
  - Without explicit state transition rules: behavior depends on host/model handling
94
94
  - Context Compiler: applies the deterministic resulting transition when
95
- `docker` is absent and `use podman` is otherwise valid; other semantic
95
+ `docker` is present and `use podman` is otherwise valid; other semantic
96
96
  conflicts may still error
97
97
 
98
98
  ### Lifecycle enforcement
@@ -137,8 +137,9 @@ Use Context Compiler in your host application first:
137
137
  ```python
138
138
  from context_compiler import (
139
139
  Engine,
140
- is_error,
141
- is_update,
140
+ NoDirectiveDecision,
141
+ SemanticErrorDecision,
142
+ UpdateDecision,
142
143
  )
143
144
 
144
145
  engine = Engine()
@@ -146,16 +147,16 @@ engine = Engine()
146
147
  user_input = "set premise current project uses uv"
147
148
  decision = engine.step(user_input)
148
149
 
149
- if is_error(decision):
150
- show_to_user(decision["message"])
151
- elif is_update(decision):
150
+ if isinstance(decision, SemanticErrorDecision):
151
+ show_to_user(decision.message)
152
+ elif isinstance(decision, UpdateDecision):
152
153
  messages = build_messages(
153
154
  premise=engine.premise,
154
155
  policies=engine.policies,
155
156
  user_input=user_input,
156
157
  )
157
158
  render(call_llm(messages))
158
- else:
159
+ elif isinstance(decision, NoDirectiveDecision):
159
160
  render(call_llm(user_input))
160
161
  ```
161
162
 
@@ -166,7 +167,7 @@ For runnable application-layer examples, see
166
167
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
167
168
  That companion repository shows enforcement points built on compiler state,
168
169
  including retrieval filtering, schema selection, tool gating, execution
169
- authorization, gateway middleware, runtime continuation handling, and prompt
170
+ authorization, gateway middleware, runtime state handling, and prompt
170
171
  construction.
171
172
 
172
173
  ## Does it Work?
@@ -213,6 +214,12 @@ for non-interactive usage.
213
214
  context-compiler --json < input.txt
214
215
  ```
215
216
 
217
+ The JSON output uses `output_version: 2`. Decision payloads expose structured
218
+ fields: updates include `changed`; semantic errors include `failure`, the
219
+ failed canonical `directive`, ordered advisory `repairs`, and `message`.
220
+ These fields are a CLI projection of ephemeral Decisions, not Decision object
221
+ serialization. Repairs are never applied automatically.
222
+
216
223
  Preload options load authoritative state:
217
224
 
218
225
  - `--initial-state-json` / `--initial-state-file` load saved state
@@ -244,9 +251,15 @@ uv sync --dev
244
251
  uv run pytest
245
252
  ```
246
253
 
254
+ CI enforces 100% coverage for the core `src/context_compiler` package. The
255
+ coverage badge represents that authoritative core-package target, not the
256
+ entire repository.
257
+
247
258
  ## Decision API
248
259
 
249
- Each user message produces a `Decision`.
260
+ Each user message produces one immutable `Decision` variant. Use concrete
261
+ variants with `isinstance` or pattern matching; use `kind` when generic code
262
+ needs the stable discriminator.
250
263
 
251
264
  ```python
252
265
  class DecisionKind(StrEnum):
@@ -254,26 +267,72 @@ class DecisionKind(StrEnum):
254
267
  UPDATE = "update"
255
268
  ERROR = "error"
256
269
 
257
- class Decision(TypedDict):
258
- kind: DecisionKind
259
- message: str | None
270
+ Decision = NoDirectiveDecision | UpdateDecision | SemanticErrorDecision
271
+
272
+ @dataclass(frozen=True, slots=True)
273
+ class NoDirectiveDecision:
274
+ kind = DecisionKind.NO_DIRECTIVE
275
+
276
+ @dataclass(frozen=True, slots=True)
277
+ class UpdateDecision:
278
+ kind = DecisionKind.UPDATE
279
+ changed: bool
280
+
281
+ @dataclass(frozen=True, slots=True)
282
+ class SemanticErrorDecision:
283
+ kind = DecisionKind.ERROR
284
+ failure: SemanticFailure
285
+ directive: CanonicalDirective
286
+ repairs: tuple[CanonicalDirective, ...]
287
+ message: str
260
288
  ```
261
289
 
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`.
290
+ `UpdateDecision.changed` reports whether authoritative state actually changed.
291
+ An accepted idempotent directive is still an `update` with
292
+ `changed=False`.
293
+
294
+ `SemanticErrorDecision.message` is derived human-readable text. Callers should
295
+ use `failure` for machine decisions rather than parsing the message.
296
+
297
+ `directive` is the canonical directive that failed semantic evaluation.
298
+ `repairs` is an ordered tuple of advisory canonical directives. Repairs are
299
+ never applied automatically; a host must explicitly submit a selected repair
300
+ through `engine.apply_directive(...)`.
301
+
302
+ The normative repair mapping is:
303
+
304
+ | Failure | Ordered advisory repairs |
305
+ | --- | --- |
306
+ | `PREMISE_ALREADY_SET` | `change premise to <requested value>` |
307
+ | `PREMISE_NOT_SET` | `set premise <requested value>` |
308
+ | `ITEM_PROHIBITED` | `remove policy <item>`; `use <item>` |
309
+ | `ITEM_ALREADY_IN_USE` | `remove policy <item>`; `prohibit <item>` |
310
+ | `REPLACEMENT_TARGET_PROHIBITED` | `remove policy <target>`; retry the original replacement directive |
311
+ | `REPLACEMENT_SOURCE_PROHIBITED` | no repair (`()`) |
312
+ | `REPLACEMENT_SOURCE_MISSING` | no repair (`()`) |
313
+
314
+ Repairs are canonical, ordered, and advisory only. The engine never applies
315
+ them automatically; hosts explicitly select and submit any repair they want to
316
+ use. `message` is presentation data, so control flow must use `failure` and
317
+ the structured directives rather than parse message text.
265
318
 
266
319
  Meaning:
267
320
 
268
321
  | kind | host behavior |
269
322
  | --- | --- |
270
323
  | 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 |
324
+ | update | canonical directive was accepted; inspect `changed` and use updated state downstream |
325
+ | error | canonical directive was rejected semantically; inspect `failure`, show `message`, and optionally offer `repairs` |
326
+
327
+ `engine.step(...)` is the raw input boundary: it parses user input, may return
328
+ `NoDirectiveDecision` when no canonical directive is produced, and performs
329
+ semantic evaluation only after canonical parsing succeeds. The canonical
330
+ execution boundary is `engine.apply_directive(...)`, which accepts only a
331
+ `CanonicalDirective` and returns only `UpdateDecision` or
332
+ `SemanticErrorDecision`.
273
333
 
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.
334
+ Grammar failures do not produce semantic errors. A semantic error is possible
335
+ only after a canonical directive has parsed successfully.
277
336
 
278
337
  See [docs/api-reference.md](docs/api-reference.md) for the full public API
279
338
  reference.
@@ -283,8 +342,8 @@ Common API entry points:
283
342
  - engine lifecycle: `Engine()`, `engine.step(...)`,
284
343
  `engine.premise`, `engine.policies`, `engine.export_json(...)`,
285
344
  `engine.import_json(...)`
286
- - decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
287
- `get_error_message(...)`
345
+ - decision variants: `NoDirectiveDecision`, `UpdateDecision`,
346
+ `SemanticErrorDecision`, `SemanticFailure`
288
347
  - state transport: `engine.export_json(...)`, `engine.import_json(...)`
289
348
 
290
349
  ---
@@ -365,8 +424,8 @@ the compiler.
365
424
 
366
425
  - They transport **authoritative state only**
367
426
  - 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
427
+ - Decision objects and advisory repairs are not persisted; persistence carries
428
+ authoritative state only
370
429
 
371
430
  ---
372
431
 
@@ -404,16 +463,20 @@ User: reset policies
404
463
  User: clear state
405
464
  ```
406
465
 
407
- Grammar invariant: one input may contain at most one canonical directive.
466
+ Grammar invariant: a single input never applies more than one canonical
467
+ directive.
408
468
  Directive-shaped invalid input is outside the canonical language, and
409
469
  `error` is reserved for canonical directives that later fail semantic
410
470
  evaluation against authoritative state.
411
471
 
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`.
472
+ A semantic error is a terminal result for the current input. It leaves
473
+ authoritative state unchanged and returns the failure classification, failed
474
+ canonical directive, ordered advisory repairs, and presentation message.
475
+ Repairs are canonical directives. They are advisory only, are never applied
476
+ automatically, and require explicit host selection and submission through
477
+ `engine.apply_directive(...)`. An absent source item in a canonical replacement
478
+ directive is a semantic `error` and does not authorize degradation to plain
479
+ `use`.
417
480
 
418
481
  Examples:
419
482
 
@@ -425,18 +488,25 @@ clear state
425
488
 
426
489
  Invalid:
427
490
  use docker and prohibit peanuts
428
- set premise vegetarian and use docker
429
491
  clear state then set premise new project
492
+
493
+ Premise payload (opaque):
494
+ set premise vegetarian and use docker
430
495
  ```
431
496
 
497
+ Policy compounds such as `use docker and prohibit peanuts` remain invalid;
498
+ premise `VALUE` is opaque and may contain directive-like words.
499
+
432
500
  Quote behavior follows the current grammar literally:
433
501
 
434
502
  ```text
435
- Passthrough:
503
+ Passthrough (`no_directive`):
436
504
  "use docker and prohibit peanuts"
437
505
 
438
- Invalid:
506
+ Invalid directive:
439
507
  use "docker and prohibit peanuts"
508
+
509
+ Canonical directive (`set premise`):
440
510
  set premise "use docker and prohibit peanuts"
441
511
  ```
442
512
 
@@ -461,8 +531,8 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
461
531
  **Isn't this just prompt reinjection?**
462
532
  No. Prompt construction is one downstream use of authoritative state.
463
533
  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
534
+ allowed, when a terminal error is required, and which advisory repairs are
535
+ available. For runnable application-layer examples, see
466
536
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
467
537
 
468
538
  Human-facing interpretation is a separate concern. If you want to recognize
@@ -483,8 +553,8 @@ User: prohibit python_script
483
553
  ```
484
554
 
485
555
  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
556
+ repair rules itself. Context Compiler applies deterministic state-transition
557
+ rules and can return a terminal error instead of silently
488
558
  overwriting state.
489
559
 
490
560
  ---
@@ -493,11 +563,11 @@ overwriting state.
493
563
 
494
564
  ### Guarantees
495
565
 
496
- - State changes only through explicit user directives or confirmation.
566
+ - State changes only through canonical directives that pass semantic evaluation.
497
567
  - Identical input sequences produce identical compiler state.
498
568
  - Model responses never modify compiler state.
499
569
  - Ambiguous directives trigger error instead of changing state.
500
- - Syntax errors never create pending continuation.
570
+ - Syntax errors never produce semantic errors or state changes.
501
571
 
502
572
  Behavioral tests and Hypothesis-based property tests verify these invariants.
503
573