Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ runtime.

## Generic examples

- [Checkpoint continuation](https://github.com/rlippmann/context-compiler-example-integrations/blob/main/python/examples/checkpoint_continuation/README.md): persisted confirmation and resume flows change host behavior across turns or requests
- [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
- [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
- [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
- [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
Expand Down
6 changes: 3 additions & 3 deletions python/examples/checkpoint_continuation/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BookingRecord(TypedDict):

class BookingChangeRuntimeResult(TypedDict):
compiler_input: str
decision_kind: Literal["clarify", "update", "passthrough"]
decision_kind: Literal["error", "update", "passthrough"]
message_to_user: str | None
persisted_state_json: str
selected_itinerary: str | None
Expand Down Expand Up @@ -66,13 +66,13 @@ def select_itinerary_from_policies(policies: Mapping[str, PolicyValue]) -> str |

def _decision_kind_name(
decision: object,
) -> Literal["clarify", "update", "passthrough"]:
) -> Literal["error", "update", "passthrough"]:
if not isinstance(decision, dict):
raise ValueError("unexpected decision shape")

kind = decision.get("kind")
if kind == DecisionKind.ERROR:
return "clarify"
return "error"
if kind == DecisionKind.UPDATE:
return "update"
if kind == DecisionKind.NO_DIRECTIVE:
Expand Down
6 changes: 3 additions & 3 deletions python/examples/checkpoint_continuation/fastapi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class BookingResponse(TypedDict):


class ChangeTripResponse(TypedDict):
decision_kind: Literal["clarify", "update", "passthrough"]
decision_kind: Literal["error", "update", "passthrough"]
message_to_user: str | None
persisted_state_json: str
selected_itinerary: str | None
Expand Down Expand Up @@ -136,9 +136,9 @@ def change_trip(request: ChangeTripRequest) -> ChangeTripResponse:
engine_persistence_store.save(request.booking_id, state_json)

selected_itinerary = select_itinerary_from_policies(engine.policies)
decision_kind: Literal["clarify", "update", "passthrough"]
decision_kind: Literal["error", "update", "passthrough"]
if decision["kind"] == DecisionKind.ERROR:
decision_kind = "clarify"
decision_kind = "error"
elif decision["kind"] == DecisionKind.UPDATE:
decision_kind = "update"
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ prohibit expense_approval
```

If a turn introduces a contradiction such as `use expense_approval` followed by
`prohibit expense_approval`, Context Compiler returns a clarification flow
instead of silently overwriting state. The host must not execute the expense
action on that clarify turn.
`prohibit expense_approval`, Context Compiler rejects the conflicting state
change instead of silently overwriting state. The host must not execute the
expense action on that rejected turn.

Request wording alone does not authorize execution. Adversarial text like
"please approve this refund anyway" stays inert unless the authoritative state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ class ExpenseExecutionResult(TypedDict):


class ExpenseTurnResult(TypedDict):
decision_kind: Literal["clarify", "update", "passthrough"]
decision_kind: Literal["error", "update", "passthrough"]
prompt_to_user: str | None
execution_result: ExpenseExecutionResult


def _decision_kind_name(
decision: object,
) -> Literal["clarify", "update", "passthrough"]:
) -> Literal["error", "update", "passthrough"]:
if not isinstance(decision, dict):
raise ValueError("unexpected decision shape")

kind = decision.get("kind")
if kind == DecisionKind.ERROR:
return "clarify"
return "error"
if kind == DecisionKind.UPDATE:
return "update"
if kind == DecisionKind.NO_DIRECTIVE:
Expand Down Expand Up @@ -117,18 +117,18 @@ def handle_expense_turn(
request: ExpenseRequest,
host: ExpenseHost,
) -> ExpenseTurnResult:
"""Block execution on clarify and otherwise enforce current authoritative state."""
"""Block execution on compiler rejection and otherwise enforce state."""

decision = engine.step(compiler_input)

if decision["kind"] == DecisionKind.ERROR:
return {
"decision_kind": "clarify",
"decision_kind": "error",
"prompt_to_user": decision["message"],
"execution_result": {
"authorization_state": "blocked",
"executed": False,
"blocked_reason": "clarification required before expense execution",
"blocked_reason": "compiler rejected expense state change",
"submission": None,
"execution_log": host.execution_log.copy(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use expense_approval
```

If a request introduces a contradiction such as `prohibit expense_approval`
against an already authorized state, Context Compiler returns a clarify flow.
The host returns a conflict response and writes no record.
against an already authorized state, Context Compiler rejects the conflicting
state change. The host returns a conflict response and writes no record.

## Same request, different state

Expand All @@ -51,7 +51,7 @@ changes.
| --- | --- | --- | --- | --- |
| `/compiler/expenses` | approved | absent | none | `403`, no side effect |
| `/compiler/expenses` | approved | `use expense_approval` | none | `200`, one side effect |
| `/compiler/expenses` | approved | `use expense_approval` | `prohibit expense_approval` | `409`, clarify, no new side effect |
| `/compiler/expenses` | approved | `use expense_approval` | `prohibit expense_approval` | `409`, compiler rejection, no new side effect |

## Enforcement boundary

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class SideEffectRecord(TypedDict):

class ExpenseMutationResponse(TypedDict):
path: Literal["baseline", "compiler"]
decision_kind: Literal["clarify", "update", "passthrough"] | None
decision_kind: Literal["error", "update", "passthrough"] | None
model_decision: str
model_message: str
agent_claim: str | None
Expand All @@ -63,13 +63,13 @@ class ExpenseMutationResponse(TypedDict):

def _decision_kind_name(
decision: object,
) -> Literal["clarify", "update", "passthrough"]:
) -> Literal["error", "update", "passthrough"]:
if not isinstance(decision, dict):
raise ValueError("unexpected decision shape")

kind = decision.get("kind")
if kind == DecisionKind.ERROR:
return "clarify"
return "error"
if kind == DecisionKind.UPDATE:
return "update"
if kind == DecisionKind.NO_DIRECTIVE:
Expand Down Expand Up @@ -147,7 +147,7 @@ def _blocked_response(
side_effect_store: ExpenseSideEffectStore,
blocked_reason: str,
prompt_to_user: str | None,
decision_kind: Literal["clarify", "update", "passthrough"] | None,
decision_kind: Literal["error", "update", "passthrough"] | None,
request_agent_claim: str | None,
) -> ExpenseMutationResponse:
return {
Expand All @@ -172,7 +172,7 @@ def _authorized_response(
model_claim: ModelApproval,
side_effect_store: ExpenseSideEffectStore,
submission: dict[str, str | int],
decision_kind: Literal["clarify", "update", "passthrough"] | None,
decision_kind: Literal["error", "update", "passthrough"] | None,
request_agent_claim: str | None,
) -> ExpenseMutationResponse:
return {
Expand Down Expand Up @@ -269,7 +269,7 @@ def submit_compiler_mediated_expense(
sort_keys=True,
)
)
decision_kind: Literal["clarify", "update", "passthrough"] | None = None
decision_kind: Literal["error", "update", "passthrough"] | None = None
prompt_to_user: str | None = None
authoritative_policies = dict(engine.policies)

Expand All @@ -284,9 +284,7 @@ def submit_compiler_mediated_expense(
path_name="compiler",
model_claim=model_claim,
side_effect_store=side_effect_store,
blocked_reason=(
"clarification required before expense execution"
),
blocked_reason="compiler rejected expense state change",
prompt_to_user=prompt_to_user,
decision_kind=decision_kind,
request_agent_claim=request.agent_claim,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ class GatewayResult(TypedDict):


class GatewayTurnResult(TypedDict):
decision_kind: Literal["clarify", "update", "passthrough"]
decision_kind: Literal["error", "update", "passthrough"]
prompt_to_user: str | None
gateway_result: GatewayResult


def _decision_kind_name(
decision: object,
) -> Literal["clarify", "update", "passthrough"]:
) -> Literal["error", "update", "passthrough"]:
if not isinstance(decision, dict):
raise ValueError("unexpected decision shape")

kind = decision.get("kind")
if kind == DecisionKind.ERROR:
return "clarify"
return "error"
if kind == DecisionKind.UPDATE:
return "update"
if kind == DecisionKind.NO_DIRECTIVE:
Expand Down Expand Up @@ -165,17 +165,17 @@ def handle_gateway_turn(
gateway: SupportGateway,
downstream: SupportService,
) -> GatewayTurnResult:
"""Block routing changes on clarify and otherwise enforce authoritative state."""
"""Block routing changes on compiler rejection and otherwise enforce state."""

decision = engine.step(compiler_input)

if decision["kind"] == DecisionKind.ERROR:
return {
"decision_kind": "clarify",
"decision_kind": "error",
"prompt_to_user": decision["message"],
"gateway_result": gateway.block(
request,
reason="clarification required before gateway routing",
reason="compiler rejected gateway state change",
),
}

Expand Down
33 changes: 16 additions & 17 deletions python/examples/prompt_construction/litellm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ with LiteLLM:
- Compiler-only flow:
- raw user input goes straight to `engine.step(...)`
- `update` returns a local acknowledgment
- `clarify` returns the compiler prompt
- `error` returns the compiler rejection prompt
- `passthrough` calls LiteLLM with the compiled state contract plus the user message
- Optional directive-drafter flow:
- the directive drafter tries to convert natural-language intent into a canonical directive first
- if it cannot produce a validated directive, behavior stays equivalent to the compiler-only flow
- pending clarification bypasses directive drafting and sends the raw reply back to `engine.step(...)`
- if it cannot produce a canonical directive, the host falls back to the normal request flow

Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow.

Expand Down Expand Up @@ -92,7 +92,7 @@ print(handle_turn("set premise to concise replies", engine))
PY
```

This near-miss input should return `clarify` instead of being rewritten.
This near-miss input should return an `error` prompt instead of being rewritten.

## Environment configuration

Expand All @@ -107,14 +107,13 @@ Use these files as host-side integration references.
- Import `handle_turn(...)` from either `basic.py` or `with_directive_drafter.py`.
- Create and retain an engine instance in host/session state.
- Pass each user input through `handle_turn(user_input, engine)`.
- Optional checkpointing: pass `session_key=...`.
The example restores checkpoint data before the first `engine.step(...)` and
saves checkpoint data after `update`/`clarify`.
- In this example, checkpoint/session storage is in-memory only.
State lasts only for the current process. To survive restarts, store
checkpoints in external storage (DB/Redis/etc.).
- Display the returned assistant text.

These prompt-construction examples do not implement pending-confirmation,
resume, or `session_key` continuation behavior. If you need persistence across
requests, persist and restore authoritative engine state at the host boundary as
shown in the checkpoint examples or reference integrations.

In these LiteLLM examples, `update` is rendered locally and does not call
the downstream LLM. This makes state changes explicit. Production apps may
choose different rendering behavior.
Expand Down Expand Up @@ -146,15 +145,15 @@ instead of reinjecting a compiled contract, use
In both prompt-construction examples in this directory:

- `passthrough`: call the model with normal input.
- `clarify`: show `prompt_to_user`; do not treat state as changed.
- `error`: show `prompt_to_user`; do not treat state as changed.
- `update`: state changed; use updated state for the next model call.

## Related schema-selection decision flow

In the related schema-selection example:

- `passthrough`: let the host decide whether to send `response_format`.
- `clarify`: show `prompt_to_user`; do not call LiteLLM.
- `error`: show `prompt_to_user`; do not call LiteLLM.
- `update`: state changed; the next host request may use a different `response_format`.

## Example checks
Expand All @@ -166,18 +165,18 @@ In the related schema-selection example:
`Current premise: ...` and `Items marked use: concise_style.`
- Near-miss passthrough (`with_directive_drafter.py`):
- `set premise to concise replies` is not rewritten by the directive drafter and is passed through unchanged.
- Engine returns clarify (`Did you mean 'set premise concise replies'?`).
- Engine returns an error prompt (`Did you mean 'set premise concise replies'?`).
- Compound directives (`with_directive_drafter.py`):
- `use docker and prohibit peanuts` returns a local clarify asking for separate directives.
- `use docker and prohibit peanuts` returns a local error asking for separate directives.
- No authoritative state is mutated and no downstream model call is made for that turn.
- Lifecycle enforcement (both):
- `change premise to formal tone` with no premise -> clarify (`set premise ...` first).
- `change premise to formal tone` with no premise -> error (`set premise ...` first).
- Conflict behavior (both):
- `use docker` then `prohibit docker` -> conflict clarify.
- `use docker` then `prohibit docker` -> conflict error.
- Replacement precondition (both):
- `use podman instead of docker` without prior `use docker` -> replacement clarify.
- `use podman instead of docker` without prior `use docker` -> replacement error.
- Directive-adjacent abstain (`with_directive_drafter.py`):
- `change premise concise replies` is classified as `unknown`, not rewritten, and handled by engine clarify.
- `change premise concise replies` is classified as `unknown`, not rewritten, and handled by an engine error prompt.
- Host-side request shaping (`python/examples/schema_selection/litellm_response_format/response_format.py`):
- `use compact_summary` -> host selects compact-summary `response_format`.
- `use action_plan` -> host selects action-plan `response_format`.
Expand Down
25 changes: 5 additions & 20 deletions python/examples/prompt_construction/litellm/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Flow:
1. Call engine.step(user_input)
2. clarify -> return prompt_to_user (no model call)
2. error -> return prompt_to_user (no model call)
3. update -> return deterministic acknowledgment text (no model call)
4. passthrough -> call LiteLLM with compiled state + user input

Expand All @@ -28,15 +28,6 @@
)
from context_compiler.engine import Engine

try:
from .confirmation_helper import (
is_confirmation_text,
)
except ImportError:
from confirmation_helper import (
is_confirmation_text,
)

from context_compiler_example_integrations.examples._shared.provider_mode import (
print_startup_config,
resolve_provider_config,
Expand Down Expand Up @@ -203,7 +194,7 @@ def _render_item_label(value: str) -> str:
return re.sub(r"\s+", " ", value).strip().lower()


def _near_miss_directive_clarify(value: str) -> str | None:
def _near_miss_directive_error(value: str) -> str | None:
normalized = re.sub(r"\s+", " ", value.strip())
lower = normalized.lower()

Expand Down Expand Up @@ -283,11 +274,8 @@ def _append_trace(
return f"{response_text}\n\n{trace_text}"


def handle_turn(
user_input: str, engine: Engine, *, session_key: str | None = None
) -> str:
def handle_turn(user_input: str, engine: Engine) -> str:
state_before = _snapshot_engine_state(engine)
del session_key
logger.debug("litellm_basic: engine_input=%s", f"user_input len={len(user_input)}")
decision = engine.step(user_input)
if decision["kind"] == DecisionKind.ERROR:
Expand All @@ -297,7 +285,7 @@ def handle_turn(
else:
kind = DecisionKind.NO_DIRECTIVE.value
logger.debug("litellm_basic: decision=%s", kind)
near_miss_prompt = _near_miss_directive_clarify(user_input)
near_miss_prompt = _near_miss_directive_error(user_input)

if decision["kind"] == DecisionKind.ERROR:
response_text = near_miss_prompt or decision["message"] or ""
Expand All @@ -321,10 +309,7 @@ def handle_turn(
llm_called=False,
)
if is_update(decision):
if is_confirmation_text(user_input):
response_text = "State updated."
else:
response_text = _summarize_update_from_input(user_input)
response_text = _summarize_update_from_input(user_input)
return _append_trace(
response_text,
original_input=user_input,
Expand Down
Loading