context-compiler-example-integrations 0.1.2__tar.gz → 0.2.0__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_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/PKG-INFO +7 -7
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/pyproject.toml +5 -5
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/README.md +2 -2
- context_compiler_example_integrations-0.2.0/python/examples/checkpoint_continuation/README.md +65 -0
- context_compiler_example_integrations-0.2.0/python/examples/checkpoint_continuation/example.py +163 -0
- context_compiler_example_integrations-0.2.0/python/examples/checkpoint_continuation/fastapi/README.md +65 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/checkpoint_continuation/fastapi/app.py +65 -69
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/README.md +4 -4
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/example.py +39 -40
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/fastapi/README.md +3 -3
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/fastapi/app.py +58 -35
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/gateway_middleware/customer_support_routing/example.py +35 -40
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/litellm/README.md +32 -23
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/litellm/basic.py +65 -120
- context_compiler_example_integrations-0.2.0/python/examples/prompt_construction/litellm/with_directive_drafter.py +371 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/writing_assistant/README.md +1 -1
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/writing_assistant/example.py +39 -43
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/README.md +2 -3
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +41 -45
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/hr_policy_lookup/README.md +1 -1
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/hr_policy_lookup/example.py +47 -48
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/litellm_response_format/response_format.py +15 -23
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/ollama_structured_output/README.md +1 -1
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/ollama_structured_output/example.py +15 -23
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/refund_intake/example.py +13 -12
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/calendar_admin/example.py +40 -43
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/mcp_calendar_admin/README.md +1 -1
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/mcp_calendar_admin/example.py +49 -54
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/mcp_calendar_admin/live_model.py +65 -33
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/litellm_proxy/README.md +17 -16
- context_compiler_example_integrations-0.2.0/python/reference_integrations/litellm_proxy/_litellm_support.py +34 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/litellm_proxy/context_compiler_precall_hook.py +20 -56
- context_compiler_example_integrations-0.2.0/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +184 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/openwebui_pipe/README.md +57 -38
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/openwebui_pipe/open_webui_pipe.py +115 -136
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +276 -368
- context_compiler_example_integrations-0.1.2/python/examples/checkpoint_continuation/README.md +0 -77
- context_compiler_example_integrations-0.1.2/python/examples/checkpoint_continuation/example.py +0 -169
- context_compiler_example_integrations-0.1.2/python/examples/checkpoint_continuation/fastapi/README.md +0 -85
- context_compiler_example_integrations-0.1.2/python/examples/prompt_construction/litellm/confirmation_helper.py +0 -91
- context_compiler_example_integrations-0.1.2/python/examples/prompt_construction/litellm/with_directive_drafter.py +0 -524
- context_compiler_example_integrations-0.1.2/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +0 -289
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/.gitignore +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/LICENSE +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/context_compiler_example_integrations/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/context_compiler_example_integrations/examples/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/context_compiler_example_integrations/reference_integrations/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/_shared/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/_shared/litellm_request.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/_shared/provider_mode.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/fastapi/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/execution_authorization/expense_approval/fastapi/model_approval.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/gateway_middleware/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/gateway_middleware/customer_support_routing/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/gateway_middleware/customer_support_routing/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/prompt_construction/writing_assistant/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/retrieval_filtering/hr_policy_lookup/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/refund_intake/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/schema_selection/refund_intake/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/calendar_admin/README.md +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/calendar_admin/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/examples/tool_gating/mcp_calendar_admin/__init__.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/litellm_proxy/_checkpoint_support.py +0 -0
- {context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/python/reference_integrations/litellm_proxy/config.example.yaml +0 -0
{context_compiler_example_integrations-0.1.2 → context_compiler_example_integrations-0.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: context-compiler-example-integrations
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Example integrations and enforcement-point demonstrations for Context Compiler.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rlippmann/context-compiler-example-integrations
|
|
6
6
|
Project-URL: Repository, https://github.com/rlippmann/context-compiler-example-integrations
|
|
@@ -21,14 +21,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
22
|
Classifier: Topic :: Software Development :: Testing
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
|
-
Requires-Dist: context-compiler
|
|
24
|
+
Requires-Dist: context-compiler<0.10,>=0.9.1
|
|
25
25
|
Provides-Extra: all
|
|
26
26
|
Requires-Dist: chromadb; extra == 'all'
|
|
27
|
-
Requires-Dist: context-compiler-directive-drafter
|
|
27
|
+
Requires-Dist: context-compiler-directive-drafter<0.3,>=0.2.0; extra == 'all'
|
|
28
28
|
Requires-Dist: fastapi; extra == 'all'
|
|
29
29
|
Requires-Dist: litellm; extra == 'all'
|
|
30
30
|
Provides-Extra: drafter
|
|
31
|
-
Requires-Dist: context-compiler-directive-drafter
|
|
31
|
+
Requires-Dist: context-compiler-directive-drafter<0.3,>=0.2.0; extra == 'drafter'
|
|
32
32
|
Provides-Extra: fastapi
|
|
33
33
|
Requires-Dist: fastapi; extra == 'fastapi'
|
|
34
34
|
Provides-Extra: litellm
|
|
@@ -61,7 +61,7 @@ pip install "context-compiler-example-integrations"
|
|
|
61
61
|
|
|
62
62
|
That installs the shared core dependency only:
|
|
63
63
|
|
|
64
|
-
- `context-compiler>=0.
|
|
64
|
+
- `context-compiler>=0.9.1,<0.10`
|
|
65
65
|
|
|
66
66
|
Add extras only for the examples you want to inspect locally:
|
|
67
67
|
|
|
@@ -77,7 +77,7 @@ runtime.
|
|
|
77
77
|
|
|
78
78
|
## Generic examples
|
|
79
79
|
|
|
80
|
-
- [Checkpoint continuation](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/checkpoint_continuation/README.md): persisted
|
|
80
|
+
- [Checkpoint continuation](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/checkpoint_continuation/README.md): persisted authoritative state changes host behavior across turns or requests
|
|
81
81
|
- [Execution authorization](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/execution_authorization/README.md): protected host actions execute only when authoritative state allows them
|
|
82
82
|
- [Gateway middleware](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/gateway_middleware/README.md): the host allows, blocks, or routes requests before downstream work runs
|
|
83
83
|
- [Prompt construction](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/prompt_construction/README.md): the host builds different request or prompt payloads from authoritative state
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "context-compiler-example-integrations"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Example integrations and enforcement-point demonstrations for Context Compiler."
|
|
9
9
|
readme = "python/README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = { text = "Apache-2.0" }
|
|
12
12
|
authors = [{ name = "Robert Lippmann" }]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"context-compiler>=0.
|
|
14
|
+
"context-compiler>=0.9.1,<0.10",
|
|
15
15
|
]
|
|
16
16
|
keywords = [
|
|
17
17
|
"context-compiler",
|
|
@@ -47,12 +47,12 @@ Issues = "https://github.com/rlippmann/context-compiler-example-integrations/iss
|
|
|
47
47
|
[project.optional-dependencies]
|
|
48
48
|
all = [
|
|
49
49
|
"chromadb",
|
|
50
|
-
"context-compiler-directive-drafter>=0.
|
|
50
|
+
"context-compiler-directive-drafter>=0.2.0,<0.3",
|
|
51
51
|
"fastapi",
|
|
52
52
|
"litellm",
|
|
53
53
|
]
|
|
54
54
|
drafter = [
|
|
55
|
-
"context-compiler-directive-drafter>=0.
|
|
55
|
+
"context-compiler-directive-drafter>=0.2.0,<0.3",
|
|
56
56
|
]
|
|
57
57
|
fastapi = [
|
|
58
58
|
"fastapi",
|
|
@@ -67,7 +67,7 @@ retrieval = [
|
|
|
67
67
|
[dependency-groups]
|
|
68
68
|
dev = [
|
|
69
69
|
"chromadb",
|
|
70
|
-
"context-compiler-directive-drafter>=0.
|
|
70
|
+
"context-compiler-directive-drafter>=0.2.0,<0.3",
|
|
71
71
|
"fastapi",
|
|
72
72
|
"httpx2>=2.5.0",
|
|
73
73
|
"httpx>=0.28.1",
|
|
@@ -22,7 +22,7 @@ pip install "context-compiler-example-integrations"
|
|
|
22
22
|
|
|
23
23
|
That installs the shared core dependency only:
|
|
24
24
|
|
|
25
|
-
- `context-compiler>=0.
|
|
25
|
+
- `context-compiler>=0.9.1,<0.10`
|
|
26
26
|
|
|
27
27
|
Add extras only for the examples you want to inspect locally:
|
|
28
28
|
|
|
@@ -38,7 +38,7 @@ runtime.
|
|
|
38
38
|
|
|
39
39
|
## Generic examples
|
|
40
40
|
|
|
41
|
-
- [Checkpoint continuation](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/checkpoint_continuation/README.md): persisted
|
|
41
|
+
- [Checkpoint continuation](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/checkpoint_continuation/README.md): persisted authoritative state changes host behavior across turns or requests
|
|
42
42
|
- [Execution authorization](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/execution_authorization/README.md): protected host actions execute only when authoritative state allows them
|
|
43
43
|
- [Gateway middleware](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/gateway_middleware/README.md): the host allows, blocks, or routes requests before downstream work runs
|
|
44
44
|
- [Prompt construction](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/prompt_construction/README.md): the host builds different request or prompt payloads from authoritative state
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# State Persistence
|
|
2
|
+
|
|
3
|
+
Persisting authoritative compiler state lets a fresh host process recover the
|
|
4
|
+
same premise and policy decisions without recreating them from model output or
|
|
5
|
+
conversation history. This example shows state persistence in a generic Python
|
|
6
|
+
travel-booking flow.
|
|
7
|
+
|
|
8
|
+
## Domain
|
|
9
|
+
|
|
10
|
+
The domain is a small travel-booking change flow.
|
|
11
|
+
|
|
12
|
+
The user selects a new itinerary, the compiler records that selection in
|
|
13
|
+
authoritative state, and the host later applies the booking change from a
|
|
14
|
+
restored engine.
|
|
15
|
+
|
|
16
|
+
## Runtime
|
|
17
|
+
|
|
18
|
+
This is a generic Python example.
|
|
19
|
+
|
|
20
|
+
It does not call an LLM.
|
|
21
|
+
|
|
22
|
+
It does not use directive drafter.
|
|
23
|
+
|
|
24
|
+
## What Context Compiler owns
|
|
25
|
+
|
|
26
|
+
Context Compiler owns:
|
|
27
|
+
|
|
28
|
+
- authoritative policy state
|
|
29
|
+
- serialization of that state through `export_json()`
|
|
30
|
+
- restoration of that state through `import_json()`
|
|
31
|
+
|
|
32
|
+
## What the host owns
|
|
33
|
+
|
|
34
|
+
The host owns:
|
|
35
|
+
|
|
36
|
+
- the booking record
|
|
37
|
+
- persisted state storage
|
|
38
|
+
- process boundaries
|
|
39
|
+
- the runtime behavior that actually applies the itinerary change
|
|
40
|
+
|
|
41
|
+
The host reads restored authoritative Context Compiler state and decides whether
|
|
42
|
+
to apply the booking change.
|
|
43
|
+
|
|
44
|
+
## Example behavior
|
|
45
|
+
|
|
46
|
+
1. The host starts with a booking on `boston_trip`.
|
|
47
|
+
2. The user selects `chicago_trip`.
|
|
48
|
+
3. Context Compiler updates authoritative state.
|
|
49
|
+
4. The host persists that state JSON.
|
|
50
|
+
5. A fresh host process restores the saved state into a new engine.
|
|
51
|
+
6. The host applies the booking change from the restored authoritative state.
|
|
52
|
+
|
|
53
|
+
## Run
|
|
54
|
+
|
|
55
|
+
From the repository root:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv run python python/examples/checkpoint_continuation/example.py
|
|
59
|
+
uv run pytest python/tests/test_checkpoint_continuation_example.py
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## FastAPI variant
|
|
63
|
+
|
|
64
|
+
For a request-boundary example, see
|
|
65
|
+
[python/examples/checkpoint_continuation/fastapi/README.md](fastapi/README.md).
|
context_compiler_example_integrations-0.2.0/python/examples/checkpoint_continuation/example.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Minimal persistence example for a travel booking change."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Literal, TypedDict
|
|
6
|
+
|
|
7
|
+
from context_compiler import Decision, DecisionKind, Engine, POLICY_USE, PolicyValue
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BookingRecord(TypedDict):
|
|
11
|
+
booking_id: str
|
|
12
|
+
active_itinerary: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BookingChangeRuntimeResult(TypedDict):
|
|
16
|
+
compiler_input: str
|
|
17
|
+
decision_kind: Literal["error", "update", "passthrough"]
|
|
18
|
+
message_to_user: str | None
|
|
19
|
+
persisted_state_json: str
|
|
20
|
+
selected_itinerary: str | None
|
|
21
|
+
host_applied_change: bool
|
|
22
|
+
active_itinerary: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class EnginePersistenceStore:
|
|
27
|
+
"""Host-owned persistence for serialized authoritative compiler state."""
|
|
28
|
+
|
|
29
|
+
saved_state_json: str | None = None
|
|
30
|
+
|
|
31
|
+
def save(self, state_json: str) -> None:
|
|
32
|
+
self.saved_state_json = state_json
|
|
33
|
+
|
|
34
|
+
def load(self) -> str:
|
|
35
|
+
if self.saved_state_json is None:
|
|
36
|
+
raise ValueError("no saved state")
|
|
37
|
+
return self.saved_state_json
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class BookingHost:
|
|
42
|
+
"""Host-owned runtime behavior for the booking example."""
|
|
43
|
+
|
|
44
|
+
booking: BookingRecord
|
|
45
|
+
applied_changes: list[str] = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
def apply_selected_itinerary(self, policies: Mapping[str, PolicyValue]) -> bool:
|
|
48
|
+
selected_itinerary = select_itinerary_from_policies(policies)
|
|
49
|
+
if selected_itinerary is None:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
self.booking["active_itinerary"] = selected_itinerary
|
|
53
|
+
self.applied_changes.append(selected_itinerary)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def select_itinerary_from_policies(policies: Mapping[str, PolicyValue]) -> str | None:
|
|
58
|
+
"""Select the host-visible itinerary from authoritative state."""
|
|
59
|
+
|
|
60
|
+
for item, kind in policies.items():
|
|
61
|
+
if kind == POLICY_USE:
|
|
62
|
+
return item
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _decision_kind_name(
|
|
67
|
+
decision: Decision,
|
|
68
|
+
) -> Literal["error", "update", "passthrough"]:
|
|
69
|
+
kind = decision.kind
|
|
70
|
+
if kind == DecisionKind.ERROR:
|
|
71
|
+
return "error"
|
|
72
|
+
if kind == DecisionKind.UPDATE:
|
|
73
|
+
return "update"
|
|
74
|
+
if kind == DecisionKind.NO_DIRECTIVE:
|
|
75
|
+
return "passthrough"
|
|
76
|
+
raise ValueError(f"unexpected decision kind: {kind}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def persist_itinerary_selection(
|
|
80
|
+
engine: Engine,
|
|
81
|
+
*,
|
|
82
|
+
requested_itinerary: str,
|
|
83
|
+
) -> BookingChangeRuntimeResult:
|
|
84
|
+
"""Persist authoritative state after selecting an itinerary."""
|
|
85
|
+
|
|
86
|
+
compiler_input = f"use {requested_itinerary}"
|
|
87
|
+
decision = engine.step(compiler_input)
|
|
88
|
+
persisted_state_json = engine.export_json()
|
|
89
|
+
selected_itinerary = select_itinerary_from_policies(engine.policies)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"compiler_input": compiler_input,
|
|
93
|
+
"decision_kind": _decision_kind_name(decision),
|
|
94
|
+
"message_to_user": decision.message
|
|
95
|
+
if decision.kind == DecisionKind.ERROR
|
|
96
|
+
else None,
|
|
97
|
+
"persisted_state_json": persisted_state_json,
|
|
98
|
+
"selected_itinerary": selected_itinerary,
|
|
99
|
+
"host_applied_change": False,
|
|
100
|
+
"active_itinerary": requested_itinerary
|
|
101
|
+
if selected_itinerary is not None
|
|
102
|
+
else "boston_trip",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def restore_engine_from_persisted_state(state_json: str) -> Engine:
|
|
107
|
+
"""Restore authoritative compiler state into a fresh engine."""
|
|
108
|
+
|
|
109
|
+
engine = Engine()
|
|
110
|
+
engine.import_json(state_json)
|
|
111
|
+
return engine
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def apply_restored_itinerary(
|
|
115
|
+
engine: Engine, host: BookingHost
|
|
116
|
+
) -> BookingChangeRuntimeResult:
|
|
117
|
+
"""Apply host behavior from restored authoritative compiler state."""
|
|
118
|
+
|
|
119
|
+
host_applied_change = host.apply_selected_itinerary(engine.policies)
|
|
120
|
+
selected_itinerary = select_itinerary_from_policies(engine.policies)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"compiler_input": "",
|
|
124
|
+
"decision_kind": "update" if host_applied_change else "passthrough",
|
|
125
|
+
"message_to_user": None,
|
|
126
|
+
"persisted_state_json": engine.export_json(),
|
|
127
|
+
"selected_itinerary": selected_itinerary,
|
|
128
|
+
"host_applied_change": host_applied_change,
|
|
129
|
+
"active_itinerary": host.booking["active_itinerary"],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def run_demo() -> dict[str, BookingChangeRuntimeResult | str]:
|
|
134
|
+
"""Run a deterministic persistence demonstration."""
|
|
135
|
+
|
|
136
|
+
initial_booking: BookingRecord = {
|
|
137
|
+
"booking_id": "booking-100",
|
|
138
|
+
"active_itinerary": "boston_trip",
|
|
139
|
+
}
|
|
140
|
+
first_engine = Engine()
|
|
141
|
+
engine_persistence_store = EnginePersistenceStore()
|
|
142
|
+
|
|
143
|
+
persisted_result = persist_itinerary_selection(
|
|
144
|
+
first_engine,
|
|
145
|
+
requested_itinerary="chicago_trip",
|
|
146
|
+
)
|
|
147
|
+
engine_persistence_store.save(persisted_result["persisted_state_json"])
|
|
148
|
+
|
|
149
|
+
restored_engine = restore_engine_from_persisted_state(
|
|
150
|
+
engine_persistence_store.load()
|
|
151
|
+
)
|
|
152
|
+
restored_host = BookingHost(booking=initial_booking.copy())
|
|
153
|
+
applied_result = apply_restored_itinerary(restored_engine, restored_host)
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"persisted_result": persisted_result,
|
|
157
|
+
"applied_result": applied_result,
|
|
158
|
+
"saved_state_json": engine_persistence_store.load(),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
print(run_demo())
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# State Persistence with FastAPI
|
|
2
|
+
|
|
3
|
+
Saved authoritative compiler state lets later HTTP requests recover the same
|
|
4
|
+
policy decisions instead of starting over. This example shows state
|
|
5
|
+
persistence across stateless HTTP request boundaries.
|
|
6
|
+
|
|
7
|
+
## Enforcement Point
|
|
8
|
+
|
|
9
|
+
Authoritative state persistence
|
|
10
|
+
|
|
11
|
+
## Domain
|
|
12
|
+
|
|
13
|
+
The domain is a small travel-booking change flow.
|
|
14
|
+
|
|
15
|
+
The first request selects `chicago_trip` in compiler state. A later request
|
|
16
|
+
restores that saved state and lets the host apply the booking change.
|
|
17
|
+
|
|
18
|
+
## Runtime
|
|
19
|
+
|
|
20
|
+
This is a small FastAPI example.
|
|
21
|
+
|
|
22
|
+
FastAPI is secondary to the enforcement point.
|
|
23
|
+
|
|
24
|
+
It exists to show that the host can persist authoritative state between
|
|
25
|
+
separate HTTP requests and restore it later into a fresh engine.
|
|
26
|
+
|
|
27
|
+
## Ownership Boundary
|
|
28
|
+
|
|
29
|
+
Context Compiler owns:
|
|
30
|
+
|
|
31
|
+
- authoritative policy state
|
|
32
|
+
- state export through `export_json()`
|
|
33
|
+
- state restore through `import_json()`
|
|
34
|
+
|
|
35
|
+
The host owns:
|
|
36
|
+
|
|
37
|
+
- persisted state storage
|
|
38
|
+
- request routing
|
|
39
|
+
- booking mutation
|
|
40
|
+
|
|
41
|
+
In this example, the host creates a fresh engine per request.
|
|
42
|
+
|
|
43
|
+
The second request applies the saved itinerary only because the host restores
|
|
44
|
+
the persisted authoritative state, not because the process remembered a
|
|
45
|
+
conversation.
|
|
46
|
+
|
|
47
|
+
## Endpoints
|
|
48
|
+
|
|
49
|
+
- `POST /change-trip`
|
|
50
|
+
- updates authoritative state with `use chicago_trip`
|
|
51
|
+
- persists the resulting state JSON in the host store
|
|
52
|
+
- `POST /apply-trip`
|
|
53
|
+
- restores the saved state JSON into a fresh engine
|
|
54
|
+
- applies the booking change from restored policy state
|
|
55
|
+
- `GET /booking`
|
|
56
|
+
- returns the host-owned booking state
|
|
57
|
+
|
|
58
|
+
## Validate
|
|
59
|
+
|
|
60
|
+
From the repository root:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run pytest python/tests/test_fastapi_checkpoint_continuation_example.py
|
|
64
|
+
uv run pre-commit run --all-files
|
|
65
|
+
```
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
"""Small FastAPI
|
|
1
|
+
"""Small FastAPI persistence example for travel booking."""
|
|
2
2
|
|
|
3
|
-
from collections.abc import Callable
|
|
3
|
+
from collections.abc import Callable, Mapping
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from typing import Literal
|
|
6
6
|
|
|
7
|
-
from context_compiler import
|
|
8
|
-
from context_compiler.engine import Checkpoint, Engine
|
|
7
|
+
from context_compiler import DecisionKind, Engine, POLICY_USE, PolicyValue
|
|
9
8
|
from fastapi import FastAPI, HTTPException
|
|
10
9
|
from pydantic import BaseModel
|
|
11
10
|
from typing_extensions import TypedDict
|
|
@@ -22,17 +21,16 @@ class BookingResponse(TypedDict):
|
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class ChangeTripResponse(TypedDict):
|
|
25
|
-
decision_kind: Literal["
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
decision_kind: Literal["error", "update", "passthrough"]
|
|
25
|
+
message_to_user: str | None
|
|
26
|
+
persisted_state_json: str
|
|
27
|
+
selected_itinerary: str | None
|
|
28
28
|
booking: BookingResponse
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
32
|
-
decision_kind: Literal["clarify", "update", "passthrough"]
|
|
33
|
-
prompt_to_user: str | None
|
|
34
|
-
checkpoint_pending: bool
|
|
31
|
+
class ApplyTripResponse(TypedDict):
|
|
35
32
|
host_applied_change: bool
|
|
33
|
+
selected_itinerary: str | None
|
|
36
34
|
booking: BookingResponse
|
|
37
35
|
|
|
38
36
|
|
|
@@ -40,28 +38,23 @@ class ChangeTripRequest(BaseModel):
|
|
|
40
38
|
booking_id: str
|
|
41
39
|
|
|
42
40
|
|
|
43
|
-
class ConfirmRequest(BaseModel):
|
|
44
|
-
booking_id: str
|
|
45
|
-
user_input: str
|
|
46
|
-
|
|
47
|
-
|
|
48
41
|
@dataclass
|
|
49
|
-
class
|
|
50
|
-
"""Host-owned
|
|
42
|
+
class EnginePersistenceStore:
|
|
43
|
+
"""Host-owned authoritative state persistence for stateless HTTP requests."""
|
|
51
44
|
|
|
52
|
-
|
|
45
|
+
states_by_booking_id: dict[str, str] = field(default_factory=dict)
|
|
53
46
|
|
|
54
|
-
def save(self, booking_id: str,
|
|
55
|
-
self.
|
|
47
|
+
def save(self, booking_id: str, state_json: str) -> None:
|
|
48
|
+
self.states_by_booking_id[booking_id] = state_json
|
|
56
49
|
|
|
57
|
-
def load(self, booking_id: str) ->
|
|
58
|
-
|
|
59
|
-
if
|
|
50
|
+
def load(self, booking_id: str) -> str:
|
|
51
|
+
state_json = self.states_by_booking_id.get(booking_id)
|
|
52
|
+
if state_json is None:
|
|
60
53
|
raise KeyError(booking_id)
|
|
61
|
-
return
|
|
54
|
+
return state_json
|
|
62
55
|
|
|
63
56
|
def has(self, booking_id: str) -> bool:
|
|
64
|
-
return booking_id in self.
|
|
57
|
+
return booking_id in self.states_by_booking_id
|
|
65
58
|
|
|
66
59
|
|
|
67
60
|
@dataclass
|
|
@@ -85,8 +78,10 @@ class BookingHost:
|
|
|
85
78
|
booking_store: BookingStore
|
|
86
79
|
applied_changes: list[str] = field(default_factory=list)
|
|
87
80
|
|
|
88
|
-
def apply_selected_itinerary(
|
|
89
|
-
|
|
81
|
+
def apply_selected_itinerary(
|
|
82
|
+
self, booking_id: str, policies: Mapping[str, PolicyValue]
|
|
83
|
+
) -> bool:
|
|
84
|
+
selected_itinerary = select_itinerary_from_policies(policies)
|
|
90
85
|
if selected_itinerary is None:
|
|
91
86
|
return False
|
|
92
87
|
|
|
@@ -96,42 +91,35 @@ class BookingHost:
|
|
|
96
91
|
return True
|
|
97
92
|
|
|
98
93
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
94
|
+
def select_itinerary_from_policies(policies: Mapping[str, PolicyValue]) -> str | None:
|
|
95
|
+
for item, kind in policies.items():
|
|
96
|
+
if kind == POLICY_USE:
|
|
97
|
+
return item
|
|
98
|
+
return None
|
|
104
99
|
|
|
105
100
|
|
|
106
|
-
def
|
|
107
|
-
engine =
|
|
108
|
-
engine.
|
|
101
|
+
def restore_engine_from_persisted_state(state_json: str) -> Engine:
|
|
102
|
+
engine = Engine()
|
|
103
|
+
engine.import_json(state_json)
|
|
109
104
|
return engine
|
|
110
105
|
|
|
111
106
|
|
|
112
|
-
def restore_engine_from_authoritative_state_only(checkpoint: Checkpoint) -> Engine:
|
|
113
|
-
authoritative_state = checkpoint["authoritative_state"]
|
|
114
|
-
return create_engine(state=authoritative_state)
|
|
115
|
-
|
|
116
|
-
|
|
117
107
|
def _fresh_engine() -> Engine:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return create_engine()
|
|
108
|
+
return Engine()
|
|
121
109
|
|
|
122
110
|
|
|
123
111
|
def create_app(
|
|
124
112
|
*,
|
|
125
|
-
|
|
113
|
+
engine_persistence_store: EnginePersistenceStore | None = None,
|
|
126
114
|
booking_store: BookingStore | None = None,
|
|
127
115
|
engine_factory: Callable[[], Engine] = _fresh_engine,
|
|
128
116
|
) -> FastAPI:
|
|
129
|
-
|
|
117
|
+
engine_persistence_store = engine_persistence_store or EnginePersistenceStore()
|
|
130
118
|
booking_store = booking_store or BookingStore()
|
|
131
119
|
booking_host = BookingHost(booking_store=booking_store)
|
|
132
120
|
|
|
133
|
-
app = FastAPI(title="
|
|
134
|
-
app.state.
|
|
121
|
+
app = FastAPI(title="state-persistence-fastapi-example")
|
|
122
|
+
app.state.engine_persistence_store = engine_persistence_store
|
|
135
123
|
app.state.booking_store = booking_store
|
|
136
124
|
app.state.booking_host = booking_host
|
|
137
125
|
app.state.engine_factory = engine_factory
|
|
@@ -141,43 +129,51 @@ def create_app(
|
|
|
141
129
|
booking = booking_store.get_or_create(request.booking_id)
|
|
142
130
|
engine = engine_factory()
|
|
143
131
|
|
|
144
|
-
compiler_input =
|
|
132
|
+
compiler_input = "use chicago_trip"
|
|
145
133
|
decision = engine.step(compiler_input)
|
|
146
|
-
|
|
134
|
+
state_json = engine.export_json()
|
|
135
|
+
engine_persistence_store.save(request.booking_id, state_json)
|
|
136
|
+
|
|
137
|
+
selected_itinerary = select_itinerary_from_policies(engine.policies)
|
|
138
|
+
decision_kind: Literal["error", "update", "passthrough"]
|
|
139
|
+
if decision.kind == DecisionKind.ERROR:
|
|
140
|
+
decision_kind = "error"
|
|
141
|
+
elif decision.kind == DecisionKind.UPDATE:
|
|
142
|
+
decision_kind = "update"
|
|
143
|
+
else:
|
|
144
|
+
decision_kind = "passthrough"
|
|
147
145
|
|
|
148
146
|
return {
|
|
149
|
-
"decision_kind":
|
|
150
|
-
"
|
|
151
|
-
|
|
147
|
+
"decision_kind": decision_kind,
|
|
148
|
+
"message_to_user": decision.message
|
|
149
|
+
if decision.kind == DecisionKind.ERROR
|
|
150
|
+
else None,
|
|
151
|
+
"persisted_state_json": state_json,
|
|
152
|
+
"selected_itinerary": selected_itinerary,
|
|
152
153
|
"booking": {
|
|
153
154
|
"booking_id": booking["booking_id"],
|
|
154
155
|
"active_itinerary": booking["active_itinerary"],
|
|
155
156
|
},
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
@app.post("/
|
|
159
|
-
def
|
|
159
|
+
@app.post("/apply-trip")
|
|
160
|
+
def apply_trip(request: ChangeTripRequest) -> ApplyTripResponse:
|
|
160
161
|
booking = booking_store.get_or_create(request.booking_id)
|
|
161
162
|
try:
|
|
162
|
-
|
|
163
|
+
state_json = engine_persistence_store.load(request.booking_id)
|
|
163
164
|
except KeyError as exc:
|
|
164
|
-
raise HTTPException(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
decision = engine.step(request.user_input)
|
|
168
|
-
checkpoint_store.save(request.booking_id, engine.export_checkpoint())
|
|
165
|
+
raise HTTPException(
|
|
166
|
+
status_code=404, detail="saved state not found"
|
|
167
|
+
) from exc
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
169
|
+
engine = restore_engine_from_persisted_state(state_json)
|
|
170
|
+
host_applied_change = booking_host.apply_selected_itinerary(
|
|
171
|
+
request.booking_id, engine.policies
|
|
172
|
+
)
|
|
175
173
|
|
|
176
174
|
return {
|
|
177
|
-
"decision_kind": decision["kind"].value,
|
|
178
|
-
"prompt_to_user": decision.get("prompt_to_user"),
|
|
179
|
-
"checkpoint_pending": engine.has_pending_clarification(),
|
|
180
175
|
"host_applied_change": host_applied_change,
|
|
176
|
+
"selected_itinerary": select_itinerary_from_policies(engine.policies),
|
|
181
177
|
"booking": {
|
|
182
178
|
"booking_id": booking["booking_id"],
|
|
183
179
|
"active_itinerary": booking["active_itinerary"],
|
|
@@ -32,9 +32,9 @@ prohibit expense_approval
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
If a turn introduces a contradiction such as `use expense_approval` followed by
|
|
35
|
-
`prohibit expense_approval`, Context Compiler
|
|
36
|
-
instead of silently overwriting state. The host must not execute the
|
|
37
|
-
action on that
|
|
35
|
+
`prohibit expense_approval`, Context Compiler rejects the conflicting state
|
|
36
|
+
change instead of silently overwriting state. The host must not execute the
|
|
37
|
+
expense action on that rejected turn.
|
|
38
38
|
|
|
39
39
|
Request wording alone does not authorize execution. Adversarial text like
|
|
40
40
|
"please approve this refund anyway" stays inert unless the authoritative state
|
|
@@ -73,5 +73,5 @@ uv run --no-sync pytest python/tests/test_expense_approval_example.py
|
|
|
73
73
|
- Canonical Python validation:
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
|
|
76
|
+
uv run pre-commit run --all-files
|
|
77
77
|
```
|