context-compiler 0.9.0.dev3__tar.gz → 0.9.0.dev5__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.
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/PKG-INFO +46 -58
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/README.md +45 -49
- context_compiler-0.9.0.dev3/demos/01_llm_contradiction_clarify.py → context_compiler-0.9.0.dev5/demos/01_llm_contradiction_error.py +35 -24
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/02_llm_constraint_guardrail.py +15 -7
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/03_llm_premise_guardrail.py +22 -4
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/04_llm_tool_denylist_guardrail.py +15 -7
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/05_llm_prompt_drift_vs_state.py +19 -7
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/06_llm_context_compaction.py +5 -4
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/07_llm_prompt_vs_state.py +38 -10
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/08_llm_replacement_precondition.py +22 -12
- context_compiler-0.9.0.dev3/demos/09_llm_confirmation_passthrough.py → context_compiler-0.9.0.dev5/demos/09_llm_confirmation_no_directive.py +39 -30
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/README.md +2 -2
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/common.py +72 -45
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/run_demo.py +2 -2
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/01_persistent_guardrails.py +11 -3
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/02_configuration_and_correction.py +7 -3
- context_compiler-0.9.0.dev3/examples/03_ambiguity_with_clarification.py → context_compiler-0.9.0.dev5/examples/03_ambiguity_with_error.py +11 -7
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/04_tool_governance_denylist.py +6 -3
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/05_llm_integration_pattern.py +17 -14
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/06_step_sequence_and_state_restore.py +9 -3
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/07_single_policy_correction.py +6 -2
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/examples/README.md +4 -11
- context_compiler-0.9.0.dev5/examples/_util.py +71 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/pyproject.toml +10 -1
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/__init__.py +10 -24
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/const.py +2 -2
- context_compiler-0.9.0.dev5/src/context_compiler/decision_helpers.py +30 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/engine.py +115 -86
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/grammar.py +54 -13
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/repl.py +121 -150
- context_compiler-0.9.0.dev3/examples/08_controller_preview_diff.py +0 -42
- context_compiler-0.9.0.dev3/examples/_util.py +0 -56
- context_compiler-0.9.0.dev3/src/context_compiler/audit.py +0 -23
- context_compiler-0.9.0.dev3/src/context_compiler/controller.py +0 -149
- context_compiler-0.9.0.dev3/src/context_compiler/decision_helpers.py +0 -24
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/.gitignore +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/LICENSE +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/__init__.py +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/demos/llm_client.py +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/host_support/__init__.py +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/host_support/provider_mode.py +0 -0
- {context_compiler-0.9.0.dev3 → context_compiler-0.9.0.dev5}/src/context_compiler/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: context-compiler
|
|
3
|
-
Version: 0.9.0.
|
|
3
|
+
Version: 0.9.0.dev5
|
|
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
|
|
@@ -18,14 +18,6 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Provides-Extra: demos
|
|
20
20
|
Requires-Dist: litellm>=1.0.0; extra == 'demos'
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: coverage; extra == 'dev'
|
|
23
|
-
Requires-Dist: hypothesis; extra == 'dev'
|
|
24
|
-
Requires-Dist: mypy<2.0,>=1.12; extra == 'dev'
|
|
25
|
-
Requires-Dist: pre-commit; extra == 'dev'
|
|
26
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
27
|
-
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
-
Requires-Dist: ruff<1.0,>=0.12; extra == 'dev'
|
|
29
21
|
Description-Content-Type: text/markdown
|
|
30
22
|
|
|
31
23
|
# Context Compiler
|
|
@@ -36,7 +28,7 @@ Description-Content-Type: text/markdown
|
|
|
36
28
|
|
|
37
29
|
Context Compiler is a deterministic conversational state authority for LLM applications.
|
|
38
30
|
It handles canonical directive execution, semantic validation, deterministic
|
|
39
|
-
|
|
31
|
+
error decisions, runtime semantic continuation boundaries, and structured authoritative state for
|
|
40
32
|
the host.
|
|
41
33
|
|
|
42
34
|
## What Context Compiler provides
|
|
@@ -44,7 +36,7 @@ the host.
|
|
|
44
36
|
Context Compiler gives hosts fixed state rules:
|
|
45
37
|
|
|
46
38
|
- handle canonical explicit state changes with deterministic rules
|
|
47
|
-
-
|
|
39
|
+
- error instead of silent overwrite for blocked/ambiguous changes
|
|
48
40
|
- preserve supported pending continuation when explicit confirmation is required
|
|
49
41
|
- export and import authoritative state for host-managed persistence
|
|
50
42
|
- produce structured authoritative state for downstream host decisions
|
|
@@ -89,7 +81,7 @@ Context Compiler makes state-change rules explicit so behavior stays repeatable.
|
|
|
89
81
|
The architecture has three layers:
|
|
90
82
|
|
|
91
83
|
- syntax classification decides whether input is a canonical directive, invalid
|
|
92
|
-
directive-shaped syntax, or ordinary
|
|
84
|
+
directive-shaped syntax, or ordinary no_directive
|
|
93
85
|
- semantic evaluation decides whether a canonical directive updates state,
|
|
94
86
|
clarifies, or no-ops
|
|
95
87
|
- semantic continuation optionally preserves a deterministic blocked transition
|
|
@@ -123,7 +115,7 @@ use podman instead of docker
|
|
|
123
115
|
- Without explicit state transition rules: behavior depends on host/model handling
|
|
124
116
|
- Context Compiler: applies the deterministic resulting transition when
|
|
125
117
|
`docker` is absent and `use podman` is otherwise valid; other semantic
|
|
126
|
-
conflicts may still
|
|
118
|
+
conflicts may still error
|
|
127
119
|
|
|
128
120
|
### Lifecycle enforcement
|
|
129
121
|
|
|
@@ -133,7 +125,7 @@ change premise to formal tone
|
|
|
133
125
|
```
|
|
134
126
|
|
|
135
127
|
- Without explicit transition checks: behavior depends on host/model handling
|
|
136
|
-
- Context Compiler: asks for
|
|
128
|
+
- Context Compiler: asks for error and keeps saved state unchanged
|
|
137
129
|
|
|
138
130
|
---
|
|
139
131
|
|
|
@@ -150,9 +142,9 @@ Decision
|
|
|
150
142
|
│
|
|
151
143
|
▼
|
|
152
144
|
Host Application
|
|
153
|
-
├─
|
|
154
|
-
├─
|
|
155
|
-
└─ update → authoritative state mutated; host may
|
|
145
|
+
├─ error → ask user
|
|
146
|
+
├─ no_directive → no canonical directive recognized; host decides what to do next
|
|
147
|
+
└─ update → authoritative state mutated; host may use compiled state downstream
|
|
156
148
|
```
|
|
157
149
|
|
|
158
150
|
The compiler never calls the LLM. Your app decides what to do with the returned
|
|
@@ -167,8 +159,7 @@ Use Context Compiler in your host application first:
|
|
|
167
159
|
```python
|
|
168
160
|
from context_compiler import (
|
|
169
161
|
create_engine,
|
|
170
|
-
|
|
171
|
-
is_clarify,
|
|
162
|
+
is_error,
|
|
172
163
|
is_update,
|
|
173
164
|
)
|
|
174
165
|
|
|
@@ -177,10 +168,14 @@ engine = create_engine()
|
|
|
177
168
|
user_input = "set premise current project uses uv"
|
|
178
169
|
decision = engine.step(user_input)
|
|
179
170
|
|
|
180
|
-
if
|
|
181
|
-
show_to_user(
|
|
171
|
+
if is_error(decision):
|
|
172
|
+
show_to_user(decision["message"])
|
|
182
173
|
elif is_update(decision):
|
|
183
|
-
messages = build_messages(
|
|
174
|
+
messages = build_messages(
|
|
175
|
+
premise=engine.premise,
|
|
176
|
+
policies=engine.policies,
|
|
177
|
+
user_input=user_input,
|
|
178
|
+
)
|
|
184
179
|
render(call_llm(messages))
|
|
185
180
|
else:
|
|
186
181
|
render(call_llm(user_input))
|
|
@@ -224,10 +219,9 @@ Preload options load authoritative state:
|
|
|
224
219
|
- `--initial-state-json` / `--initial-state-file` load saved state
|
|
225
220
|
(via exported state JSON).
|
|
226
221
|
|
|
227
|
-
REPL commands (
|
|
222
|
+
REPL commands (not engine directives):
|
|
228
223
|
|
|
229
224
|
- `state` shows current saved state.
|
|
230
|
-
- `preview <input>` runs deterministic dry-run without mutating live state.
|
|
231
225
|
- `step <input>` is an explicit alias of normal bare-input step behavior.
|
|
232
226
|
|
|
233
227
|
Bare REPL input behavior remains unchanged.
|
|
@@ -268,7 +262,7 @@ Packaging notes:
|
|
|
268
262
|
### Development
|
|
269
263
|
|
|
270
264
|
```bash
|
|
271
|
-
uv sync --
|
|
265
|
+
uv sync --dev
|
|
272
266
|
uv run pytest
|
|
273
267
|
```
|
|
274
268
|
|
|
@@ -277,22 +271,30 @@ uv run pytest
|
|
|
277
271
|
Each user message produces a `Decision`.
|
|
278
272
|
|
|
279
273
|
```python
|
|
274
|
+
class DecisionKind(StrEnum):
|
|
275
|
+
NO_DIRECTIVE = "no_directive"
|
|
276
|
+
UPDATE = "update"
|
|
277
|
+
ERROR = "error"
|
|
278
|
+
|
|
280
279
|
class Decision(TypedDict):
|
|
281
|
-
kind:
|
|
282
|
-
|
|
283
|
-
prompt_to_user: str | None
|
|
280
|
+
kind: DecisionKind
|
|
281
|
+
message: str | None
|
|
284
282
|
```
|
|
285
283
|
|
|
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`.
|
|
287
|
+
|
|
286
288
|
Meaning:
|
|
287
289
|
|
|
288
290
|
| kind | host behavior |
|
|
289
291
|
| --- | --- |
|
|
290
|
-
|
|
|
291
|
-
| update | authoritative state mutated; host may
|
|
292
|
-
|
|
|
292
|
+
| 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 |
|
|
293
295
|
|
|
294
|
-
For normal app code, prefer the exported decision helpers (`
|
|
295
|
-
`is_update`, `
|
|
296
|
+
For normal app code, prefer the exported decision helpers (`is_error`,
|
|
297
|
+
`is_update`, `is_no_directive`, `get_error_message`)
|
|
296
298
|
instead of direct key traversal.
|
|
297
299
|
|
|
298
300
|
See [docs/api-reference.md](docs/api-reference.md) for the full public API
|
|
@@ -300,26 +302,12 @@ reference.
|
|
|
300
302
|
|
|
301
303
|
Common API entry points:
|
|
302
304
|
|
|
303
|
-
- engine lifecycle: `create_engine(
|
|
305
|
+
- engine lifecycle: `create_engine()`, `engine.step(...)`,
|
|
304
306
|
`engine.premise`, `engine.policies`, `engine.export_json(...)`,
|
|
305
307
|
`engine.import_json(...)`
|
|
306
|
-
- decision helpers: `
|
|
307
|
-
`
|
|
308
|
+
- decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
|
|
309
|
+
`get_error_message(...)`
|
|
308
310
|
- state transport: `engine.export_json(...)`, `engine.import_json(...)`
|
|
309
|
-
- controller API: `step(...)`
|
|
310
|
-
- audit APIs: `preview(...)`, `state_diff(...)`
|
|
311
|
-
|
|
312
|
-
### Controller And Audit APIs (Reusable Outside REPL)
|
|
313
|
-
|
|
314
|
-
- `step(engine, user_input)` returns a reusable result envelope around one
|
|
315
|
-
engine turn
|
|
316
|
-
- `preview(engine, user_input)` performs a deterministic dry run and restores
|
|
317
|
-
live engine state afterward
|
|
318
|
-
- `state_diff(state_before, state_after)` summarizes structural state changes
|
|
319
|
-
|
|
320
|
-
For examples and helper accessors such as `get_step_decision(...)`,
|
|
321
|
-
`get_preview_state_after(...)`, `preview_would_mutate(...)`, and
|
|
322
|
-
`diff_has_changes(...)`, see [docs/api-reference.md](docs/api-reference.md).
|
|
323
311
|
|
|
324
312
|
---
|
|
325
313
|
|
|
@@ -343,8 +331,8 @@ Identical input sequences always produce identical state.
|
|
|
343
331
|
For live engine-owned reads, use `engine.premise` and `engine.policies`.
|
|
344
332
|
`engine.policies` returns a caller-owned copy.
|
|
345
333
|
|
|
346
|
-
`engine.
|
|
347
|
-
|
|
334
|
+
Use `engine.export_json()` and `engine.import_json()` for persistence and
|
|
335
|
+
restoration.
|
|
348
336
|
|
|
349
337
|
---
|
|
350
338
|
|
|
@@ -441,14 +429,14 @@ User: clear state
|
|
|
441
429
|
|
|
442
430
|
Grammar invariant: one input may contain at most one canonical directive.
|
|
443
431
|
Directive-shaped invalid input is outside the canonical language, and
|
|
444
|
-
`
|
|
432
|
+
`error` is reserved for canonical directives that later fail semantic
|
|
445
433
|
evaluation against authoritative state.
|
|
446
434
|
|
|
447
435
|
Pending continuation is a separate runtime layer. It may exist only after a
|
|
448
|
-
canonical directive reaches a supported semantic `
|
|
436
|
+
canonical directive reaches a supported semantic `error` case. It never
|
|
449
437
|
repairs malformed syntax or reinterprets non-canonical input as a directive.
|
|
450
438
|
An absent source item in a canonical replacement directive is not, by itself,
|
|
451
|
-
such a `
|
|
439
|
+
such a `error` case.
|
|
452
440
|
|
|
453
441
|
Examples:
|
|
454
442
|
|
|
@@ -496,7 +484,7 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
|
|
|
496
484
|
**Isn't this just prompt reinjection?**
|
|
497
485
|
No. Prompt construction is one downstream use of authoritative state.
|
|
498
486
|
Context Compiler is the authority layer that decides when state changes are
|
|
499
|
-
allowed, when
|
|
487
|
+
allowed, when error is required, and how continuation state is
|
|
500
488
|
restored. For runnable application-layer examples, see
|
|
501
489
|
[`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
|
|
502
490
|
|
|
@@ -519,7 +507,7 @@ User: prohibit python_script
|
|
|
519
507
|
|
|
520
508
|
Without an authority layer, the application must invent conflict-resolution and
|
|
521
509
|
continuation rules itself. Context Compiler applies deterministic
|
|
522
|
-
state-transition rules and can return
|
|
510
|
+
state-transition rules and can return error instead of silently
|
|
523
511
|
overwriting state.
|
|
524
512
|
|
|
525
513
|
---
|
|
@@ -531,7 +519,7 @@ overwriting state.
|
|
|
531
519
|
- State changes only through explicit user directives or confirmation.
|
|
532
520
|
- Identical input sequences produce identical compiler state.
|
|
533
521
|
- Model responses never modify compiler state.
|
|
534
|
-
- Ambiguous directives trigger
|
|
522
|
+
- Ambiguous directives trigger error instead of changing state.
|
|
535
523
|
- Syntax errors never create pending continuation.
|
|
536
524
|
|
|
537
525
|
Behavioral tests and Hypothesis-based property tests verify these invariants.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Context Compiler is a deterministic conversational state authority for LLM applications.
|
|
8
8
|
It handles canonical directive execution, semantic validation, deterministic
|
|
9
|
-
|
|
9
|
+
error decisions, runtime semantic continuation boundaries, and structured authoritative state for
|
|
10
10
|
the host.
|
|
11
11
|
|
|
12
12
|
## What Context Compiler provides
|
|
@@ -14,7 +14,7 @@ the host.
|
|
|
14
14
|
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
18
|
- preserve supported pending continuation when explicit confirmation is required
|
|
19
19
|
- export and import authoritative state for host-managed persistence
|
|
20
20
|
- produce structured authoritative state for downstream host decisions
|
|
@@ -59,7 +59,7 @@ Context Compiler makes state-change rules explicit so behavior stays repeatable.
|
|
|
59
59
|
The architecture has three layers:
|
|
60
60
|
|
|
61
61
|
- syntax classification decides whether input is a canonical directive, invalid
|
|
62
|
-
directive-shaped syntax, or ordinary
|
|
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
65
|
- semantic continuation optionally preserves a deterministic blocked transition
|
|
@@ -93,7 +93,7 @@ use podman instead of docker
|
|
|
93
93
|
- Without explicit state transition rules: behavior depends on host/model handling
|
|
94
94
|
- Context Compiler: applies the deterministic resulting transition when
|
|
95
95
|
`docker` is absent and `use podman` is otherwise valid; other semantic
|
|
96
|
-
conflicts may still
|
|
96
|
+
conflicts may still error
|
|
97
97
|
|
|
98
98
|
### Lifecycle enforcement
|
|
99
99
|
|
|
@@ -103,7 +103,7 @@ change premise to formal tone
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
- Without explicit transition checks: behavior depends on host/model handling
|
|
106
|
-
- Context Compiler: asks for
|
|
106
|
+
- Context Compiler: asks for error and keeps saved state unchanged
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
@@ -120,9 +120,9 @@ Decision
|
|
|
120
120
|
│
|
|
121
121
|
▼
|
|
122
122
|
Host Application
|
|
123
|
-
├─
|
|
124
|
-
├─
|
|
125
|
-
└─ update → authoritative state mutated; host may
|
|
123
|
+
├─ error → ask user
|
|
124
|
+
├─ no_directive → no canonical directive recognized; host decides what to do next
|
|
125
|
+
└─ update → authoritative state mutated; host may use compiled state downstream
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
The compiler never calls the LLM. Your app decides what to do with the returned
|
|
@@ -137,8 +137,7 @@ Use Context Compiler in your host application first:
|
|
|
137
137
|
```python
|
|
138
138
|
from context_compiler import (
|
|
139
139
|
create_engine,
|
|
140
|
-
|
|
141
|
-
is_clarify,
|
|
140
|
+
is_error,
|
|
142
141
|
is_update,
|
|
143
142
|
)
|
|
144
143
|
|
|
@@ -147,10 +146,14 @@ engine = create_engine()
|
|
|
147
146
|
user_input = "set premise current project uses uv"
|
|
148
147
|
decision = engine.step(user_input)
|
|
149
148
|
|
|
150
|
-
if
|
|
151
|
-
show_to_user(
|
|
149
|
+
if is_error(decision):
|
|
150
|
+
show_to_user(decision["message"])
|
|
152
151
|
elif is_update(decision):
|
|
153
|
-
messages = build_messages(
|
|
152
|
+
messages = build_messages(
|
|
153
|
+
premise=engine.premise,
|
|
154
|
+
policies=engine.policies,
|
|
155
|
+
user_input=user_input,
|
|
156
|
+
)
|
|
154
157
|
render(call_llm(messages))
|
|
155
158
|
else:
|
|
156
159
|
render(call_llm(user_input))
|
|
@@ -194,10 +197,9 @@ Preload options load authoritative state:
|
|
|
194
197
|
- `--initial-state-json` / `--initial-state-file` load saved state
|
|
195
198
|
(via exported state JSON).
|
|
196
199
|
|
|
197
|
-
REPL commands (
|
|
200
|
+
REPL commands (not engine directives):
|
|
198
201
|
|
|
199
202
|
- `state` shows current saved state.
|
|
200
|
-
- `preview <input>` runs deterministic dry-run without mutating live state.
|
|
201
203
|
- `step <input>` is an explicit alias of normal bare-input step behavior.
|
|
202
204
|
|
|
203
205
|
Bare REPL input behavior remains unchanged.
|
|
@@ -238,7 +240,7 @@ Packaging notes:
|
|
|
238
240
|
### Development
|
|
239
241
|
|
|
240
242
|
```bash
|
|
241
|
-
uv sync --
|
|
243
|
+
uv sync --dev
|
|
242
244
|
uv run pytest
|
|
243
245
|
```
|
|
244
246
|
|
|
@@ -247,22 +249,30 @@ uv run pytest
|
|
|
247
249
|
Each user message produces a `Decision`.
|
|
248
250
|
|
|
249
251
|
```python
|
|
252
|
+
class DecisionKind(StrEnum):
|
|
253
|
+
NO_DIRECTIVE = "no_directive"
|
|
254
|
+
UPDATE = "update"
|
|
255
|
+
ERROR = "error"
|
|
256
|
+
|
|
250
257
|
class Decision(TypedDict):
|
|
251
|
-
kind:
|
|
252
|
-
|
|
253
|
-
prompt_to_user: str | None
|
|
258
|
+
kind: DecisionKind
|
|
259
|
+
message: str | None
|
|
254
260
|
```
|
|
255
261
|
|
|
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`.
|
|
265
|
+
|
|
256
266
|
Meaning:
|
|
257
267
|
|
|
258
268
|
| kind | host behavior |
|
|
259
269
|
| --- | --- |
|
|
260
|
-
|
|
|
261
|
-
| update | authoritative state mutated; host may
|
|
262
|
-
|
|
|
270
|
+
| 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 |
|
|
263
273
|
|
|
264
|
-
For normal app code, prefer the exported decision helpers (`
|
|
265
|
-
`is_update`, `
|
|
274
|
+
For normal app code, prefer the exported decision helpers (`is_error`,
|
|
275
|
+
`is_update`, `is_no_directive`, `get_error_message`)
|
|
266
276
|
instead of direct key traversal.
|
|
267
277
|
|
|
268
278
|
See [docs/api-reference.md](docs/api-reference.md) for the full public API
|
|
@@ -270,26 +280,12 @@ reference.
|
|
|
270
280
|
|
|
271
281
|
Common API entry points:
|
|
272
282
|
|
|
273
|
-
- engine lifecycle: `create_engine(
|
|
283
|
+
- engine lifecycle: `create_engine()`, `engine.step(...)`,
|
|
274
284
|
`engine.premise`, `engine.policies`, `engine.export_json(...)`,
|
|
275
285
|
`engine.import_json(...)`
|
|
276
|
-
- decision helpers: `
|
|
277
|
-
`
|
|
286
|
+
- decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
|
|
287
|
+
`get_error_message(...)`
|
|
278
288
|
- state transport: `engine.export_json(...)`, `engine.import_json(...)`
|
|
279
|
-
- controller API: `step(...)`
|
|
280
|
-
- audit APIs: `preview(...)`, `state_diff(...)`
|
|
281
|
-
|
|
282
|
-
### Controller And Audit APIs (Reusable Outside REPL)
|
|
283
|
-
|
|
284
|
-
- `step(engine, user_input)` returns a reusable result envelope around one
|
|
285
|
-
engine turn
|
|
286
|
-
- `preview(engine, user_input)` performs a deterministic dry run and restores
|
|
287
|
-
live engine state afterward
|
|
288
|
-
- `state_diff(state_before, state_after)` summarizes structural state changes
|
|
289
|
-
|
|
290
|
-
For examples and helper accessors such as `get_step_decision(...)`,
|
|
291
|
-
`get_preview_state_after(...)`, `preview_would_mutate(...)`, and
|
|
292
|
-
`diff_has_changes(...)`, see [docs/api-reference.md](docs/api-reference.md).
|
|
293
289
|
|
|
294
290
|
---
|
|
295
291
|
|
|
@@ -313,8 +309,8 @@ Identical input sequences always produce identical state.
|
|
|
313
309
|
For live engine-owned reads, use `engine.premise` and `engine.policies`.
|
|
314
310
|
`engine.policies` returns a caller-owned copy.
|
|
315
311
|
|
|
316
|
-
`engine.
|
|
317
|
-
|
|
312
|
+
Use `engine.export_json()` and `engine.import_json()` for persistence and
|
|
313
|
+
restoration.
|
|
318
314
|
|
|
319
315
|
---
|
|
320
316
|
|
|
@@ -411,14 +407,14 @@ User: clear state
|
|
|
411
407
|
|
|
412
408
|
Grammar invariant: one input may contain at most one canonical directive.
|
|
413
409
|
Directive-shaped invalid input is outside the canonical language, and
|
|
414
|
-
`
|
|
410
|
+
`error` is reserved for canonical directives that later fail semantic
|
|
415
411
|
evaluation against authoritative state.
|
|
416
412
|
|
|
417
413
|
Pending continuation is a separate runtime layer. It may exist only after a
|
|
418
|
-
canonical directive reaches a supported semantic `
|
|
414
|
+
canonical directive reaches a supported semantic `error` case. It never
|
|
419
415
|
repairs malformed syntax or reinterprets non-canonical input as a directive.
|
|
420
416
|
An absent source item in a canonical replacement directive is not, by itself,
|
|
421
|
-
such a `
|
|
417
|
+
such a `error` case.
|
|
422
418
|
|
|
423
419
|
Examples:
|
|
424
420
|
|
|
@@ -466,7 +462,7 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
|
|
|
466
462
|
**Isn't this just prompt reinjection?**
|
|
467
463
|
No. Prompt construction is one downstream use of authoritative state.
|
|
468
464
|
Context Compiler is the authority layer that decides when state changes are
|
|
469
|
-
allowed, when
|
|
465
|
+
allowed, when error is required, and how continuation state is
|
|
470
466
|
restored. For runnable application-layer examples, see
|
|
471
467
|
[`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
|
|
472
468
|
|
|
@@ -489,7 +485,7 @@ User: prohibit python_script
|
|
|
489
485
|
|
|
490
486
|
Without an authority layer, the application must invent conflict-resolution and
|
|
491
487
|
continuation rules itself. Context Compiler applies deterministic
|
|
492
|
-
state-transition rules and can return
|
|
488
|
+
state-transition rules and can return error instead of silently
|
|
493
489
|
overwriting state.
|
|
494
490
|
|
|
495
491
|
---
|
|
@@ -501,7 +497,7 @@ overwriting state.
|
|
|
501
497
|
- State changes only through explicit user directives or confirmation.
|
|
502
498
|
- Identical input sequences produce identical compiler state.
|
|
503
499
|
- Model responses never modify compiler state.
|
|
504
|
-
- Ambiguous directives trigger
|
|
500
|
+
- Ambiguous directives trigger error instead of changing state.
|
|
505
501
|
- Syntax errors never create pending continuation.
|
|
506
502
|
|
|
507
503
|
Behavioral tests and Hypothesis-based property tests verify these invariants.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Demo 1: compiler blocks contradictory directives before model call."""
|
|
2
2
|
|
|
3
|
-
from context_compiler import create_engine
|
|
3
|
+
from context_compiler import create_engine, is_error
|
|
4
4
|
from demos.common import (
|
|
5
5
|
build_baseline_messages,
|
|
6
6
|
build_mediated_messages_from_transcript,
|
|
7
7
|
build_reinjected_messages,
|
|
8
8
|
compact_user_turns,
|
|
9
9
|
extract_tag_value,
|
|
10
|
+
observe_engine,
|
|
10
11
|
print_decision,
|
|
11
12
|
print_host_check,
|
|
12
13
|
print_messages,
|
|
@@ -14,6 +15,7 @@ from demos.common import (
|
|
|
14
15
|
print_spec_report,
|
|
15
16
|
print_tag_comparison,
|
|
16
17
|
print_user_inputs,
|
|
18
|
+
state_observations,
|
|
17
19
|
yes_no,
|
|
18
20
|
)
|
|
19
21
|
from demos.llm_client import complete_messages
|
|
@@ -25,16 +27,18 @@ def main() -> None:
|
|
|
25
27
|
print_user_inputs(user_inputs)
|
|
26
28
|
|
|
27
29
|
first = engine.step(user_inputs[0])
|
|
28
|
-
|
|
30
|
+
premise, policies = observe_engine(engine)
|
|
31
|
+
print_decision("turn 1", first, premise=premise, policies=policies)
|
|
29
32
|
second = engine.step(user_inputs[1])
|
|
30
|
-
|
|
33
|
+
premise, policies = observe_engine(engine)
|
|
34
|
+
print_decision("turn 2", second, premise=premise, policies=policies)
|
|
31
35
|
|
|
32
36
|
baseline_messages = build_baseline_messages(
|
|
33
37
|
[
|
|
34
38
|
(
|
|
35
39
|
"Interpret these directives and continue anyway: "
|
|
36
40
|
"prohibit peanuts, then use peanuts. "
|
|
37
|
-
"First line must be ACTION:<
|
|
41
|
+
"First line must be ACTION:<error|proceed>."
|
|
38
42
|
)
|
|
39
43
|
],
|
|
40
44
|
baseline_system_prompt=(
|
|
@@ -51,7 +55,7 @@ def main() -> None:
|
|
|
51
55
|
(
|
|
52
56
|
"Interpret these directives and continue anyway: "
|
|
53
57
|
"prohibit peanuts, then use peanuts. "
|
|
54
|
-
"First line must be ACTION:<
|
|
58
|
+
"First line must be ACTION:<error|proceed>."
|
|
55
59
|
)
|
|
56
60
|
],
|
|
57
61
|
premise=None,
|
|
@@ -62,14 +66,17 @@ def main() -> None:
|
|
|
62
66
|
reinjected_output = complete_messages(reinjected_messages)
|
|
63
67
|
print_model_output("Reinjected-state", reinjected_output)
|
|
64
68
|
|
|
65
|
-
if second
|
|
69
|
+
if is_error(second):
|
|
66
70
|
print_messages("compiler-mediated (full)", [])
|
|
67
|
-
mediated_output =
|
|
68
|
-
f"[no call] clarification required: {second['prompt_to_user']}\nACTION:clarify"
|
|
69
|
-
)
|
|
71
|
+
mediated_output = f"[no call] error required: {second['message']}\nACTION:error"
|
|
70
72
|
print_model_output("Compiler-mediated (full)", mediated_output)
|
|
71
73
|
else:
|
|
72
|
-
|
|
74
|
+
premise, policies = observe_engine(engine)
|
|
75
|
+
mediated_messages = build_mediated_messages_from_transcript(
|
|
76
|
+
premise=premise,
|
|
77
|
+
policies=policies,
|
|
78
|
+
user_turns=user_inputs,
|
|
79
|
+
)
|
|
73
80
|
print_messages("compiler-mediated (full)", mediated_messages)
|
|
74
81
|
mediated_output = complete_messages(mediated_messages)
|
|
75
82
|
print_model_output("Compiler-mediated (full)", mediated_output)
|
|
@@ -77,10 +84,15 @@ def main() -> None:
|
|
|
77
84
|
compacted_turns, compacted_state, compacted_prompt = compact_user_turns(user_inputs)
|
|
78
85
|
if compacted_prompt is not None:
|
|
79
86
|
print_messages("compiler-mediated + compact", [])
|
|
80
|
-
compact_output = f"[no call]
|
|
87
|
+
compact_output = f"[no call] error required: {compacted_prompt}\nACTION:error"
|
|
81
88
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
82
89
|
else:
|
|
83
|
-
|
|
90
|
+
compacted_premise, compacted_policies = state_observations(compacted_state)
|
|
91
|
+
compact_messages = build_mediated_messages_from_transcript(
|
|
92
|
+
premise=compacted_premise,
|
|
93
|
+
policies=compacted_policies,
|
|
94
|
+
user_turns=compacted_turns,
|
|
95
|
+
)
|
|
84
96
|
print_messages("compiler-mediated + compact", compact_messages)
|
|
85
97
|
compact_output = complete_messages(compact_messages)
|
|
86
98
|
print_model_output("Compiler-mediated + compact", compact_output)
|
|
@@ -89,15 +101,15 @@ def main() -> None:
|
|
|
89
101
|
baseline_action = extract_tag_value(baseline_output, "ACTION")
|
|
90
102
|
reinjected_action = extract_tag_value(reinjected_output, "ACTION")
|
|
91
103
|
compact_action = extract_tag_value(compact_output, "ACTION")
|
|
92
|
-
baseline_respects = baseline_action is not None and baseline_action.lower() == "
|
|
93
|
-
reinjected_respects = reinjected_action is not None and reinjected_action.lower() == "
|
|
94
|
-
compiler_host_blocked = second
|
|
104
|
+
baseline_respects = baseline_action is not None and baseline_action.lower() == "error"
|
|
105
|
+
reinjected_respects = reinjected_action is not None and reinjected_action.lower() == "error"
|
|
106
|
+
compiler_host_blocked = is_error(second)
|
|
95
107
|
mediated_respects = compiler_host_blocked
|
|
96
108
|
compact_respects = compacted_prompt is not None or (
|
|
97
|
-
compact_action is not None and compact_action.lower() == "
|
|
109
|
+
compact_action is not None and compact_action.lower() == "error"
|
|
98
110
|
)
|
|
99
111
|
print_host_check(
|
|
100
|
-
"
|
|
112
|
+
"ACTION_ERROR",
|
|
101
113
|
yes_no(reinjected_respects),
|
|
102
114
|
context="reinjected-state",
|
|
103
115
|
)
|
|
@@ -112,26 +124,25 @@ def main() -> None:
|
|
|
112
124
|
context="compiler-mediated + compact",
|
|
113
125
|
)
|
|
114
126
|
print_spec_report(
|
|
115
|
-
test_name="01_contradiction_block — host
|
|
127
|
+
test_name="01_contradiction_block — host error gate",
|
|
116
128
|
baseline_pass=baseline_respects,
|
|
117
129
|
reinjected_state_pass=reinjected_respects,
|
|
118
130
|
compiler_pass=mediated_respects,
|
|
119
131
|
compiler_compact_pass=compact_respects,
|
|
120
|
-
expected="host should block LLM call on contradictory directive until
|
|
132
|
+
expected="host should block LLM call on contradictory directive until error",
|
|
121
133
|
actual=(
|
|
122
|
-
"baseline proceeded instead of
|
|
134
|
+
"baseline proceeded instead of erroring; "
|
|
123
135
|
"both compiler-mediated paths blocked the LLM call"
|
|
124
136
|
if mediated_respects and compact_respects and not baseline_respects
|
|
125
137
|
else (
|
|
126
|
-
"baseline also signaled
|
|
127
|
-
"both compiler-mediated paths blocked the LLM call"
|
|
138
|
+
"baseline also signaled error; both compiler-mediated paths blocked the LLM call"
|
|
128
139
|
if baseline_respects and mediated_respects and compact_respects
|
|
129
140
|
else "at least one compiler-mediated path did not block the LLM call as expected"
|
|
130
141
|
)
|
|
131
142
|
),
|
|
132
143
|
passed=mediated_respects and compact_respects,
|
|
133
|
-
result_pass="contradictory directive blocked until
|
|
134
|
-
result_fail="contradictory directive not blocked until
|
|
144
|
+
result_pass="contradictory directive blocked until error",
|
|
145
|
+
result_fail="contradictory directive not blocked until error",
|
|
135
146
|
)
|
|
136
147
|
|
|
137
148
|
|