diff --git a/python/README.md b/python/README.md index 04b43ec..596d296 100644 --- a/python/README.md +++ b/python/README.md @@ -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 diff --git a/python/examples/checkpoint_continuation/example.py b/python/examples/checkpoint_continuation/example.py index 318abd1..f895c4f 100644 --- a/python/examples/checkpoint_continuation/example.py +++ b/python/examples/checkpoint_continuation/example.py @@ -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 @@ -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: diff --git a/python/examples/checkpoint_continuation/fastapi/app.py b/python/examples/checkpoint_continuation/fastapi/app.py index 00f2d7e..a745786 100644 --- a/python/examples/checkpoint_continuation/fastapi/app.py +++ b/python/examples/checkpoint_continuation/fastapi/app.py @@ -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 @@ -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: diff --git a/python/examples/execution_authorization/expense_approval/README.md b/python/examples/execution_authorization/expense_approval/README.md index f6b2bb1..73e4291 100644 --- a/python/examples/execution_authorization/expense_approval/README.md +++ b/python/examples/execution_authorization/expense_approval/README.md @@ -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 diff --git a/python/examples/execution_authorization/expense_approval/example.py b/python/examples/execution_authorization/expense_approval/example.py index 855c10b..d177b54 100644 --- a/python/examples/execution_authorization/expense_approval/example.py +++ b/python/examples/execution_authorization/expense_approval/example.py @@ -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: @@ -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(), }, diff --git a/python/examples/execution_authorization/expense_approval/fastapi/README.md b/python/examples/execution_authorization/expense_approval/fastapi/README.md index 7224a82..d5a31b9 100644 --- a/python/examples/execution_authorization/expense_approval/fastapi/README.md +++ b/python/examples/execution_authorization/expense_approval/fastapi/README.md @@ -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 @@ -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 diff --git a/python/examples/execution_authorization/expense_approval/fastapi/app.py b/python/examples/execution_authorization/expense_approval/fastapi/app.py index ba92d12..ea461fb 100644 --- a/python/examples/execution_authorization/expense_approval/fastapi/app.py +++ b/python/examples/execution_authorization/expense_approval/fastapi/app.py @@ -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 @@ -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: @@ -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 { @@ -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 { @@ -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) @@ -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, diff --git a/python/examples/gateway_middleware/customer_support_routing/example.py b/python/examples/gateway_middleware/customer_support_routing/example.py index e6e6685..fec251d 100644 --- a/python/examples/gateway_middleware/customer_support_routing/example.py +++ b/python/examples/gateway_middleware/customer_support_routing/example.py @@ -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: @@ -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", ), } diff --git a/python/examples/prompt_construction/litellm/README.md b/python/examples/prompt_construction/litellm/README.md index 579a585..a9fc97f 100644 --- a/python/examples/prompt_construction/litellm/README.md +++ b/python/examples/prompt_construction/litellm/README.md @@ -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. @@ -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 @@ -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. @@ -146,7 +145,7 @@ 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 @@ -154,7 +153,7 @@ In both prompt-construction examples in this directory: 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 @@ -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`. diff --git a/python/examples/prompt_construction/litellm/basic.py b/python/examples/prompt_construction/litellm/basic.py index 40b5ff4..ed35999 100644 --- a/python/examples/prompt_construction/litellm/basic.py +++ b/python/examples/prompt_construction/litellm/basic.py @@ -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 @@ -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, @@ -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() @@ -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: @@ -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 "" @@ -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, diff --git a/python/examples/prompt_construction/litellm/confirmation_helper.py b/python/examples/prompt_construction/litellm/confirmation_helper.py deleted file mode 100644 index 06c76f3..0000000 --- a/python/examples/prompt_construction/litellm/confirmation_helper.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Local confirmation helpers for the LiteLLM prompt-construction examples. - -This keeps example behavior deterministic within this repository instead of -depending on the separately versioned host_support package. -""" - -import re - -_TRAILING_CONFIRM_PUNCT_RE = re.compile(r"[.,!?]+$") - -_AFFIRMATIVE_CONFIRMATION_TOKENS = frozenset( - {"yes", "yes please", "yep", "yeah", "sure", "ok", "okay"} -) -_NEGATIVE_CONFIRMATION_TOKENS = frozenset({"no", "nope", "no thanks"}) - -CONFIRMATION_TOKENS: frozenset[str] = ( - _AFFIRMATIVE_CONFIRMATION_TOKENS | _NEGATIVE_CONFIRMATION_TOKENS -) - - -def _render_item_label(value: str) -> str: - return re.sub(r"\s+", " ", value).strip() - - -def _normalize_confirmation_text(value: str) -> str: - normalized = value.strip().lower() - normalized = re.sub(r"\s+", " ", normalized) - normalized = _TRAILING_CONFIRM_PUNCT_RE.sub("", normalized).strip() - return re.sub(r"\s+", " ", normalized) - - -def is_confirmation_text(value: str) -> bool: - return _normalize_confirmation_text(value) in CONFIRMATION_TOKENS - - -def _summarize_pending_confirmation_update(pending: object) -> str: - if not isinstance(pending, dict): - return "State updated." - - replacement = pending.get("replacement") - if not isinstance(replacement, dict): - return "State updated." - - kind = replacement.get("kind") - new_item = replacement.get("new_item") - old_item = replacement.get("old_item") - - if kind == "use_only" and isinstance(new_item, str): - new_label = _render_item_label(new_item) - if new_label: - return f"State updated: Use {new_label}." - return "State updated." - - if ( - kind == "replace_use" - and isinstance(new_item, str) - and isinstance(old_item, str) - ): - new_label = _render_item_label(new_item) - old_label = _render_item_label(old_item) - if not new_label or not old_label: - return "State updated." - - prompt = pending.get("prompt_to_user") - prohibited_old_prompt = ( - f'"{old_item}" is currently prohibited. ' - f'Did you mean to remove it and use "{new_item}" instead?' - ) - if prompt == prohibited_old_prompt: - return ( - f"State updated: Removed prohibition on {old_label}; use {new_label}." - ) - return f"State updated: Replaced {old_label} with {new_label}." - - return "State updated." - - -def summarize_confirmation_update(user_input: str, pending: object) -> str: - normalized = _normalize_confirmation_text(user_input) - if normalized in _NEGATIVE_CONFIRMATION_TOKENS: - return "State unchanged." - if normalized not in _AFFIRMATIVE_CONFIRMATION_TOKENS: - return "State updated." - return _summarize_pending_confirmation_update(pending) - - -def summarize_confirmation_update_from_checkpoint( - user_input: str, checkpoint: object -) -> str: - pending = checkpoint.get("pending") if isinstance(checkpoint, dict) else None - return summarize_confirmation_update(user_input, pending) diff --git a/python/examples/prompt_construction/litellm/with_directive_drafter.py b/python/examples/prompt_construction/litellm/with_directive_drafter.py index 8d547e1..7156ba1 100644 --- a/python/examples/prompt_construction/litellm/with_directive_drafter.py +++ b/python/examples/prompt_construction/litellm/with_directive_drafter.py @@ -5,7 +5,7 @@ 2. Ask DirectiveDrafter to draft one directive, using LiteLLM only as fallback 3. Observe the returned DraftResult and extract drafted directive text when present 4. Pass drafted directive text, or the original input, to engine.step(...) -5. If the compiler returns an error or near-miss clarify, return that text locally +5. If the compiler returns an error or near-miss rejection, return that text locally 6. If the compiler applies an update, return a deterministic acknowledgment locally 7. Otherwise call LiteLLM with compiled state + user input diff --git a/python/examples/prompt_construction/writing_assistant/example.py b/python/examples/prompt_construction/writing_assistant/example.py index ca80c34..29a75ec 100644 --- a/python/examples/prompt_construction/writing_assistant/example.py +++ b/python/examples/prompt_construction/writing_assistant/example.py @@ -45,7 +45,7 @@ class PromptMessage(TypedDict): class PromptConstructionResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None model_call_ready: bool llm_call_performed: bool @@ -57,13 +57,13 @@ class PromptConstructionResult(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: @@ -131,14 +131,14 @@ def prepare_prompt_turn( if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", + "decision_kind": "error", "prompt_to_user": decision["message"], "model_call_ready": False, "llm_call_performed": False, "messages": [], "applied_premise": None, "applied_style_labels": [], - "blocked_reason": "clarification required before prompt construction", + "blocked_reason": "compiler rejected prompt-state change", } messages, premise, style_labels = build_prompt_messages( diff --git a/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py b/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py index 626c4fa..d0deff0 100644 --- a/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +++ b/python/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py @@ -46,7 +46,7 @@ class RetrievalResult(TypedDict): class RetrievalTurnResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None retrieval_result: RetrievalResult @@ -79,13 +79,13 @@ def example_documents() -> list[PolicyDocument]: 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: @@ -259,13 +259,13 @@ def handle_retrieval_turn( if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", + "decision_kind": "error", "prompt_to_user": decision["message"], "retrieval_result": { "query": query, "eligible_document_ids": [], "returned_document_ids": [], - "blocked_reason": "clarification required before retrieval policy changes", + "blocked_reason": "compiler rejected retrieval policy change", }, } diff --git a/python/examples/retrieval_filtering/hr_policy_lookup/example.py b/python/examples/retrieval_filtering/hr_policy_lookup/example.py index 285d1ec..c1e19dd 100644 --- a/python/examples/retrieval_filtering/hr_policy_lookup/example.py +++ b/python/examples/retrieval_filtering/hr_policy_lookup/example.py @@ -39,7 +39,7 @@ class RetrievalResult(TypedDict): class RetrievalTurnResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None retrieval_result: RetrievalResult @@ -127,13 +127,13 @@ def example_documents() -> list[PolicyDocument]: 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: @@ -238,13 +238,13 @@ def handle_retrieval_turn( if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", + "decision_kind": "error", "prompt_to_user": decision["message"], "retrieval_result": { "query": query, "eligible_document_ids": [], "returned_document_ids": [], - "blocked_reason": "clarification required before retrieval policy changes", + "blocked_reason": "compiler rejected retrieval policy change", }, } diff --git a/python/examples/schema_selection/litellm_response_format/response_format.py b/python/examples/schema_selection/litellm_response_format/response_format.py index 5187004..beb4b40 100644 --- a/python/examples/schema_selection/litellm_response_format/response_format.py +++ b/python/examples/schema_selection/litellm_response_format/response_format.py @@ -70,7 +70,7 @@ class TurnPlan(TypedDict): decision_kind: str - clarify_prompt: str | None + error_prompt: str | None selected_response_format_item: str | None response_format: dict[str, Any] | None @@ -102,8 +102,8 @@ def plan_turn(user_input: str, engine: Engine) -> TurnPlan: decision = engine.step(user_input) if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", - "clarify_prompt": decision["message"], + "decision_kind": "error", + "error_prompt": decision["message"], "selected_response_format_item": None, "response_format": None, } @@ -112,7 +112,7 @@ def plan_turn(user_input: str, engine: Engine) -> TurnPlan: return { "decision_kind": str(decision["kind"].value), - "clarify_prompt": None, + "error_prompt": None, "selected_response_format_item": selected_item, "response_format": response_format, } diff --git a/python/examples/schema_selection/ollama_structured_output/README.md b/python/examples/schema_selection/ollama_structured_output/README.md index c079c47..9fde2b7 100644 --- a/python/examples/schema_selection/ollama_structured_output/README.md +++ b/python/examples/schema_selection/ollama_structured_output/README.md @@ -39,7 +39,7 @@ this host selects `python_script` schema and does not request `shell_command` sc Tests verify schema selection behavior only: - compiler state -> selected schema (or no schema) -- contradiction handling stays in compiler `clarify` +- contradiction handling stays in compiler rejection/error handling Tests do not assert exact model wording. diff --git a/python/examples/schema_selection/ollama_structured_output/example.py b/python/examples/schema_selection/ollama_structured_output/example.py index c3de141..3a95ef9 100644 --- a/python/examples/schema_selection/ollama_structured_output/example.py +++ b/python/examples/schema_selection/ollama_structured_output/example.py @@ -54,7 +54,7 @@ class TurnPlan(TypedDict): decision_kind: str - clarify_prompt: str | None + error_prompt: str | None selected_schema_item: str | None format_schema: dict[str, Any] | None @@ -80,8 +80,8 @@ def plan_turn(user_input: str, engine: Engine) -> TurnPlan: decision = engine.step(user_input) if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", - "clarify_prompt": decision["message"], + "decision_kind": "error", + "error_prompt": decision["message"], "selected_schema_item": None, "format_schema": None, } @@ -90,7 +90,7 @@ def plan_turn(user_input: str, engine: Engine) -> TurnPlan: return { "decision_kind": str(decision["kind"].value), - "clarify_prompt": None, + "error_prompt": None, "selected_schema_item": selected_item, "format_schema": format_schema, } diff --git a/python/examples/tool_gating/calendar_admin/example.py b/python/examples/tool_gating/calendar_admin/example.py index 7c1f58a..1facb62 100644 --- a/python/examples/tool_gating/calendar_admin/example.py +++ b/python/examples/tool_gating/calendar_admin/example.py @@ -36,20 +36,20 @@ class CalendarToolExecutionResult(TypedDict): class CalendarToolTurnResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None execution_result: CalendarToolExecutionResult 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: @@ -143,19 +143,19 @@ def handle_calendar_admin_turn( tool_call: CalendarToolCall, host: CalendarAdminHost, ) -> CalendarToolTurnResult: - """Block tool exposure on clarify and otherwise enforce current state.""" + """Block tool exposure 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", "tool_visible": False, "executed": False, - "blocked_reason": "clarification required before exposing calendar admin tools", + "blocked_reason": "compiler rejected calendar admin state change", "tool_result": None, "registry_snapshot": host.visible_tools(engine.policies), "execution_log": host.execution_log.copy(), diff --git a/python/examples/tool_gating/mcp_calendar_admin/README.md b/python/examples/tool_gating/mcp_calendar_admin/README.md index 8a0a9ec..7a78895 100644 --- a/python/examples/tool_gating/mcp_calendar_admin/README.md +++ b/python/examples/tool_gating/mcp_calendar_admin/README.md @@ -63,7 +63,7 @@ Outcome matrix: - `use calendar_admin`: protected tool is exposed; if the model selects it, the host executes it and writes one side effect - contradiction with `prohibit calendar_admin`: Context Compiler returns - clarify/conflict and blocks protected execution before tool execution + a compiler rejection/conflict and blocks protected execution before tool execution ### Validation diff --git a/python/examples/tool_gating/mcp_calendar_admin/example.py b/python/examples/tool_gating/mcp_calendar_admin/example.py index bd62301..a4891e2 100644 --- a/python/examples/tool_gating/mcp_calendar_admin/example.py +++ b/python/examples/tool_gating/mcp_calendar_admin/example.py @@ -41,13 +41,13 @@ class McpToolExecutionResult(TypedDict): class McpToolTurnResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None execution_result: McpToolExecutionResult class McpDecisionResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] + decision_kind: Literal["error", "update", "passthrough"] prompt_to_user: str | None exposed_tools: ExposedMcpTools execution_result: NotRequired[McpToolExecutionResult] @@ -55,13 +55,13 @@ class McpDecisionResult(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: @@ -166,19 +166,19 @@ def handle_mcp_tool_turn( tool_call: McpToolCall, host: CalendarAdminMcpHost, ) -> McpToolTurnResult: - """Block MCP tool exposure on clarify and otherwise enforce current state.""" + """Block MCP tool exposure 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", "tool_visible": False, "executed": False, - "blocked_reason": "clarification required before exposing calendar admin MCP tools", + "blocked_reason": "compiler rejected calendar admin MCP state change", "tool_result": None, "exposed_tools": host.exposed_mcp_tools(engine.policies), "execution_log": host.execution_log.copy(), @@ -208,7 +208,7 @@ def describe_exposed_mcp_tools( if decision["kind"] == DecisionKind.ERROR: return { - "decision_kind": "clarify", + "decision_kind": "error", "prompt_to_user": decision["message"], "exposed_tools": host.exposed_mcp_tools(engine.policies), } diff --git a/python/examples/tool_gating/mcp_calendar_admin/live_model.py b/python/examples/tool_gating/mcp_calendar_admin/live_model.py index 2fed623..35e5e05 100644 --- a/python/examples/tool_gating/mcp_calendar_admin/live_model.py +++ b/python/examples/tool_gating/mcp_calendar_admin/live_model.py @@ -38,7 +38,7 @@ class SideEffectRecord(TypedDict): class LiveModelResult(TypedDict): - decision_kind: Literal["clarify", "update", "passthrough"] | None + decision_kind: Literal["error", "update", "passthrough"] | None prompt_to_user: str | None exposed_tool_names: list[str] hidden_tool_names: list[str] @@ -116,13 +116,13 @@ def _load_authoritative_state( 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: @@ -305,7 +305,7 @@ def run_live_model_turn( 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 effective_policies = dict(engine.policies) @@ -326,9 +326,7 @@ def run_live_model_turn( in exposed_tool_names, "model_selected_tool_name": None, "executed": False, - "blocked_reason": ( - "clarification required before exposing calendar admin MCP tools" - ), + "blocked_reason": ("compiler rejected calendar admin MCP state change"), "tool_result": None, "execution_log": host.execution_log.copy(), "side_effect_path": str(side_effect_store.artifact_path), diff --git a/python/tests/test_calendar_admin_tool_gating_example.py b/python/tests/test_calendar_admin_tool_gating_example.py index 2b1a567..edb1c60 100644 --- a/python/tests/test_calendar_admin_tool_gating_example.py +++ b/python/tests/test_calendar_admin_tool_gating_example.py @@ -161,7 +161,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_keeps_tool_hid host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["tool_visible"] is False assert turn_result["execution_result"]["executed"] is False @@ -193,7 +193,7 @@ def test_conflicting_prohibit_then_use_requires_clarification_and_keeps_tool_hid host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["tool_visible"] is False assert turn_result["execution_result"]["executed"] is False diff --git a/python/tests/test_chromadb_retrieval_filtering_example.py b/python/tests/test_chromadb_retrieval_filtering_example.py index 126db81..77799ed 100644 --- a/python/tests/test_chromadb_retrieval_filtering_example.py +++ b/python/tests/test_chromadb_retrieval_filtering_example.py @@ -116,7 +116,7 @@ def test_retrieval_behavior_changes_when_authoritative_state_changes() -> None: ] -def test_contradictory_directives_clarify_instead_of_silent_overwrite() -> None: +def test_contradictory_directives_return_error_instead_of_silent_overwrite() -> None: engine = create_engine() engine.step(f"use {EMPLOYEE_ACCESS}") retriever = ChromaHRPolicyRetriever.build() @@ -128,10 +128,10 @@ def test_contradictory_directives_clarify_instead_of_silent_overwrite() -> None: retriever=retriever, ) - assert result["decision_kind"] == "clarify" + assert result["decision_kind"] == "error" assert result["retrieval_result"]["returned_document_ids"] == [] assert result["retrieval_result"]["blocked_reason"] == ( - "clarification required before retrieval policy changes" + "compiler rejected retrieval policy change" ) assert result["prompt_to_user"] == ( f'"{EMPLOYEE_ACCESS}" is currently in use.\n' diff --git a/python/tests/test_expense_approval_example.py b/python/tests/test_expense_approval_example.py index e9b518c..29882f2 100644 --- a/python/tests/test_expense_approval_example.py +++ b/python/tests/test_expense_approval_example.py @@ -128,9 +128,7 @@ def test_runtime_behavior_changes_only_when_authoritative_state_allows_execution assert allowed_result["execution_log"] == ["submitted:expense-104"] -def test_conflicting_use_then_prohibit_requires_clarification_and_does_not_execute() -> ( - None -): +def test_conflicting_use_then_prohibit_returns_error_and_does_not_execute() -> None: engine = create_engine() engine.step("use expense_approval") host = ExpenseHost() @@ -147,7 +145,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_does_not_execu host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["executed"] is False assert turn_result["execution_result"]["execution_log"] == [] @@ -157,9 +155,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_does_not_execu ) -def test_conflicting_prohibit_then_use_requires_clarification_and_does_not_execute() -> ( - None -): +def test_conflicting_prohibit_then_use_returns_error_and_does_not_execute() -> None: engine = prohibited_engine() host = ExpenseHost() @@ -175,7 +171,7 @@ def test_conflicting_prohibit_then_use_requires_clarification_and_does_not_execu host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["executed"] is False assert turn_result["execution_result"]["execution_log"] == [] diff --git a/python/tests/test_fastapi_expense_approval_example.py b/python/tests/test_fastapi_expense_approval_example.py index 161437e..b55f2b2 100644 --- a/python/tests/test_fastapi_expense_approval_example.py +++ b/python/tests/test_fastapi_expense_approval_example.py @@ -114,7 +114,7 @@ def test_compiler_endpoint_changes_outcome_only_when_authoritative_state_differs assert authorized_response.json()["side_effect_count"] == 1 assert len(_read_jsonl(artifact_path)) == 1 - clarify_response = client.post( + reject_response = client.post( "/compiler/expenses", json={ **base_request, @@ -127,14 +127,14 @@ def test_compiler_endpoint_changes_outcome_only_when_authoritative_state_differs }, ) - assert clarify_response.status_code == 409 - clarify_detail = clarify_response.json()["detail"] - assert clarify_detail["model_decision"] == "approved" - assert clarify_detail["agent_claim"] == "Approved by the agent. Reimburse it." - assert clarify_detail["decision_kind"] == "clarify" - assert clarify_detail["authorization_state"] == "blocked" - assert clarify_detail["executed"] is False - assert "currently in use" in clarify_detail["prompt_to_user"] + assert reject_response.status_code == 409 + reject_detail = reject_response.json()["detail"] + assert reject_detail["model_decision"] == "approved" + assert reject_detail["agent_claim"] == "Approved by the agent. Reimburse it." + assert reject_detail["decision_kind"] == "error" + assert reject_detail["authorization_state"] == "blocked" + assert reject_detail["executed"] is False + assert "currently in use" in reject_detail["prompt_to_user"] assert len(_read_jsonl(artifact_path)) == 1 @@ -222,7 +222,7 @@ def test_compiler_path_with_contradictory_directive_returns_conflict_and_writes_ detail = response.json()["detail"] assert detail["model_decision"] == "approved" assert detail["agent_claim"] == "Approved by the agent. Reimburse it." - assert detail["decision_kind"] == "clarify" + assert detail["decision_kind"] == "error" assert detail["authorization_state"] == "blocked" assert detail["executed"] is False assert "currently in use" in detail["prompt_to_user"] diff --git a/python/tests/test_gateway_middleware_example.py b/python/tests/test_gateway_middleware_example.py index 1ae4d4d..3dfddfd 100644 --- a/python/tests/test_gateway_middleware_example.py +++ b/python/tests/test_gateway_middleware_example.py @@ -149,7 +149,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_blocks() -> No downstream=downstream, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["gateway_result"]["gateway_decision"] == "blocked" assert turn_result["gateway_result"]["downstream_called"] is False assert turn_result["gateway_result"]["gateway_log"] == ["blocked:support-105"] @@ -178,7 +178,7 @@ def test_conflicting_prohibit_then_use_requires_clarification_and_blocks() -> No downstream=downstream, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["gateway_result"]["gateway_decision"] == "blocked" assert turn_result["gateway_result"]["downstream_called"] is False assert turn_result["gateway_result"]["gateway_log"] == ["blocked:support-106"] diff --git a/python/tests/test_litellm_basic.py b/python/tests/test_litellm_basic.py index d7f970e..af1476e 100644 --- a/python/tests/test_litellm_basic.py +++ b/python/tests/test_litellm_basic.py @@ -112,31 +112,7 @@ def fake_completion(**kwargs): assert "Items marked use: concise_style." in system_prompt -def test_session_key_does_not_restore_removed_confirmation_continuation( - basic_module, monkeypatch: pytest.MonkeyPatch -) -> None: - llm_calls: list[object] = [] - monkeypatch.setattr( - basic_module, - "_call_litellm", - lambda messages: llm_calls.append(messages) or "downstream reply", - ) - - first_engine = create_engine() - first = basic_module.handle_turn( - "use podman instead of docker", first_engine, session_key="session-1" - ) - second_engine = create_engine() - follow_up = basic_module.handle_turn("yes", second_engine, session_key="session-1") - - assert first == "State updated: Use podman." - assert follow_up == "downstream reply" - assert len(llm_calls) == 1 - assert dict(first_engine.policies) == {"podman": "use"} - assert dict(second_engine.policies) == {} - - -def test_near_miss_directive_returns_clarify_text_and_skips_downstream( +def test_near_miss_directive_returns_error_text_and_skips_downstream( basic_module, monkeypatch: pytest.MonkeyPatch ) -> None: llm_calls: list[object] = [] @@ -152,7 +128,7 @@ def test_near_miss_directive_returns_clarify_text_and_skips_downstream( assert llm_calls == [] -def test_confirmation_text_without_pending_flow_uses_normal_turn_handling( +def test_confirmation_like_text_uses_normal_turn_handling( basic_module, monkeypatch: pytest.MonkeyPatch ) -> None: llm_calls: list[object] = [] @@ -164,7 +140,7 @@ def test_confirmation_text_without_pending_flow_uses_normal_turn_handling( engine = create_engine() first = basic_module.handle_turn("use podman instead of docker", engine) - retry = basic_module.handle_turn("yess", engine) + retry = basic_module.handle_turn("yes", engine) assert first == "State updated: Use podman." assert retry == "downstream reply" diff --git a/python/tests/test_litellm_confirmation_helper.py b/python/tests/test_litellm_confirmation_helper.py deleted file mode 100644 index 789a19c..0000000 --- a/python/tests/test_litellm_confirmation_helper.py +++ /dev/null @@ -1,101 +0,0 @@ -from context_compiler_example_integrations.examples.prompt_construction.litellm.confirmation_helper import ( - is_confirmation_text, - summarize_confirmation_update, - summarize_confirmation_update_from_checkpoint, -) - - -def test_accepted_confirmation_tokens() -> None: - accepted = [ - "yes", - " YES ", - "yes please!", - "Yep.", - "yeah", - "sure", - "ok", - "okay??", - "no", - "Nope!", - "no thanks...", - ] - - for value in accepted: - assert is_confirmation_text(value) - - -def test_rejects_near_miss_confirmation_tokens() -> None: - rejected = ["yess", "okayy", "sure thing", "affirmative", "nop", "no thank"] - - for value in rejected: - assert not is_confirmation_text(value) - - -def test_deterministic_summary_for_use_only_confirmation() -> None: - checkpoint = { - "pending": { - "kind": "replacement", - "replacement": {"kind": "use_only", "new_item": "podman", "old_item": None}, - "prompt_to_user": 'Did you mean to use "podman" instead?', - } - } - - assert ( - summarize_confirmation_update_from_checkpoint("yes", checkpoint) - == "State updated: Use podman." - ) - - -def test_deterministic_summary_for_replacement_confirmation() -> None: - checkpoint = { - "pending": { - "kind": "replacement", - "replacement": { - "kind": "replace_use", - "new_item": "podman", - "old_item": "docker", - }, - "prompt_to_user": 'Did you mean to replace "docker" with "podman"?', - } - } - - assert ( - summarize_confirmation_update_from_checkpoint("yes please", checkpoint) - == "State updated: Replaced docker with podman." - ) - - -def test_deterministic_summary_for_prohibited_old_item_replacement() -> None: - checkpoint = { - "pending": { - "kind": "replacement", - "replacement": { - "kind": "replace_use", - "new_item": "podman", - "old_item": "docker", - }, - "prompt_to_user": ( - '"docker" is currently prohibited. ' - 'Did you mean to remove it and use "podman" instead?' - ), - } - } - - assert ( - summarize_confirmation_update_from_checkpoint("okay", checkpoint) - == "State updated: Removed prohibition on docker; use podman." - ) - - -def test_safe_fallback_on_unknown_pending_shapes() -> None: - assert ( - summarize_confirmation_update("yes", {"unexpected": "shape"}) - == "State updated." - ) - assert summarize_confirmation_update_from_checkpoint( - "yes", {"pending": "unexpected"} - ) == ("State updated.") - assert ( - summarize_confirmation_update("no", {"unexpected": "shape"}) - == "State unchanged." - ) diff --git a/python/tests/test_litellm_response_format_example.py b/python/tests/test_litellm_response_format_example.py index c16c10e..21e0f41 100644 --- a/python/tests/test_litellm_response_format_example.py +++ b/python/tests/test_litellm_response_format_example.py @@ -112,13 +112,13 @@ def test_prohibit_compact_summary_selects_no_response_format() -> None: assert plan["response_format"] is None -def test_contradiction_clarify_path_selects_no_schema() -> None: +def test_contradiction_error_path_selects_no_schema() -> None: engine = create_engine() engine.step("use compact_summary") plan = plan_turn("prohibit compact_summary", engine) - assert plan["decision_kind"] == "clarify" + assert plan["decision_kind"] == "error" assert plan["selected_response_format_item"] is None assert plan["response_format"] is None diff --git a/python/tests/test_mcp_calendar_admin_live_model.py b/python/tests/test_mcp_calendar_admin_live_model.py index 6456dc8..c0aa1f4 100644 --- a/python/tests/test_mcp_calendar_admin_live_model.py +++ b/python/tests/test_mcp_calendar_admin_live_model.py @@ -66,7 +66,7 @@ def test_live_model_tool_surface_changes_with_authoritative_state( assert allowed_result["executed"] is True assert len(_read_jsonl(artifact_path)) == 1 - clarify_result = run_live_model_turn( + reject_result = run_live_model_turn( user_intent=USER_INTENT, authoritative_state={ "premise": allowed_engine.premise, @@ -76,6 +76,6 @@ def test_live_model_tool_surface_changes_with_authoritative_state( artifact_path=artifact_path, ) - assert clarify_result["decision_kind"] == "clarify" - assert clarify_result["executed"] is False + assert reject_result["decision_kind"] == "error" + assert reject_result["executed"] is False assert len(_read_jsonl(artifact_path)) == 1 diff --git a/python/tests/test_mcp_calendar_admin_live_model_helper.py b/python/tests/test_mcp_calendar_admin_live_model_helper.py index 3bea527..b1da94a 100644 --- a/python/tests/test_mcp_calendar_admin_live_model_helper.py +++ b/python/tests/test_mcp_calendar_admin_live_model_helper.py @@ -131,7 +131,7 @@ def _unexpected_selector(**_: object) -> _SelectedToolCall: model_tool_selector=_unexpected_selector, ) - assert result["decision_kind"] == "clarify" + assert result["decision_kind"] == "error" assert result["executed"] is False assert "currently in use" in (result["prompt_to_user"] or "") assert model_called is False diff --git a/python/tests/test_mcp_calendar_admin_tool_gating_example.py b/python/tests/test_mcp_calendar_admin_tool_gating_example.py index 32e6a24..f3a3101 100644 --- a/python/tests/test_mcp_calendar_admin_tool_gating_example.py +++ b/python/tests/test_mcp_calendar_admin_tool_gating_example.py @@ -151,9 +151,7 @@ def test_runtime_behavior_changes_only_when_authoritative_state_allows_mcp_tool( assert allowed_result["executed"] is True -def test_conflicting_use_then_prohibit_requires_clarification_and_blocks_mcp_tool() -> ( - None -): +def test_conflicting_use_then_prohibit_returns_error_and_blocks_mcp_tool() -> None: engine = create_engine() engine.step("use calendar_admin") host = CalendarAdminMcpHost() @@ -171,7 +169,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_blocks_mcp_too host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["tool_visible"] is False assert [ @@ -187,7 +185,7 @@ def test_conflicting_use_then_prohibit_requires_clarification_and_blocks_mcp_too ) -def test_conflicting_prohibit_then_use_requires_clarification_and_keeps_mcp_tool_hidden() -> ( +def test_conflicting_prohibit_then_use_returns_error_and_keeps_mcp_tool_hidden() -> ( None ): engine = prohibited_engine() @@ -206,7 +204,7 @@ def test_conflicting_prohibit_then_use_requires_clarification_and_keeps_mcp_tool host=host, ) - assert turn_result["decision_kind"] == "clarify" + assert turn_result["decision_kind"] == "error" assert turn_result["execution_result"]["authorization_state"] == "blocked" assert turn_result["execution_result"]["tool_visible"] is False assert turn_result["execution_result"]["exposed_tools"]["hidden_tool_names"] == [ diff --git a/python/tests/test_ollama_structured_output_example.py b/python/tests/test_ollama_structured_output_example.py index 74ad298..f9bbb4e 100644 --- a/python/tests/test_ollama_structured_output_example.py +++ b/python/tests/test_ollama_structured_output_example.py @@ -58,13 +58,13 @@ def test_empty_or_unknown_state_selects_no_schema() -> None: assert unknown_plan["format_schema"] is None -def test_contradiction_clarify_path_selects_no_schema() -> None: +def test_contradiction_error_path_selects_no_schema() -> None: engine = create_engine() engine.step("use python_script") plan = plan_turn("prohibit python_script", engine) - assert plan["decision_kind"] == "clarify" + assert plan["decision_kind"] == "error" assert plan["selected_schema_item"] is None assert plan["format_schema"] is None diff --git a/python/tests/test_prompt_construction_writing_assistant.py b/python/tests/test_prompt_construction_writing_assistant.py index 2c4addf..5995e30 100644 --- a/python/tests/test_prompt_construction_writing_assistant.py +++ b/python/tests/test_prompt_construction_writing_assistant.py @@ -138,7 +138,7 @@ def test_adversarial_user_text_does_not_override_saved_premise_or_policy() -> No assert "verbose" not in result["messages"][0]["content"].lower() -def test_invalid_premise_lifecycle_produces_clarification_behavior() -> None: +def test_invalid_premise_lifecycle_produces_error_behavior() -> None: engine = create_engine() result = prepare_prompt_turn( @@ -147,18 +147,16 @@ def test_invalid_premise_lifecycle_produces_clarification_behavior() -> None: user_text="Please rewrite this paragraph.", ) - assert result["decision_kind"] == "clarify" + assert result["decision_kind"] == "error" assert result["messages"] == [] assert result["model_call_ready"] is False - assert result["blocked_reason"] == ( - "clarification required before prompt construction" - ) + assert result["blocked_reason"] == ("compiler rejected prompt-state change") assert result["prompt_to_user"] == ( "No premise is set.\nUse 'set premise ' to define one." ) -def test_contradictory_policy_directives_produce_clarification_behavior() -> None: +def test_contradictory_policy_directives_produce_error_behavior() -> None: engine = create_engine() engine.step(f"use {CONCISE_STYLE}") @@ -168,12 +166,10 @@ def test_contradictory_policy_directives_produce_clarification_behavior() -> Non user_text="Please rewrite this paragraph.", ) - assert result["decision_kind"] == "clarify" + assert result["decision_kind"] == "error" assert result["messages"] == [] assert result["model_call_ready"] is False - assert result["blocked_reason"] == ( - "clarification required before prompt construction" - ) + assert result["blocked_reason"] == ("compiler rejected prompt-state change") assert result["prompt_to_user"] == ( f'"{CONCISE_STYLE}" is currently in use.\n' "Remove or replace it before prohibiting it." diff --git a/python/tests/test_retrieval_filtering_example.py b/python/tests/test_retrieval_filtering_example.py index cdf8e58..8a9cada 100644 --- a/python/tests/test_retrieval_filtering_example.py +++ b/python/tests/test_retrieval_filtering_example.py @@ -220,7 +220,7 @@ def test_absent_or_unknown_premise_does_not_invent_results() -> None: assert unknown_result["returned_document_ids"] == ["employee_handbook"] -def test_contradictory_directives_clarify_instead_of_silent_overwrite() -> None: +def test_contradictory_directives_return_error_instead_of_silent_overwrite() -> None: engine = create_engine() engine.step(f"use {EMPLOYEE_ACCESS}") retriever = HRPolicyRetriever(documents=example_documents()) @@ -232,10 +232,10 @@ def test_contradictory_directives_clarify_instead_of_silent_overwrite() -> None: retriever=retriever, ) - assert result["decision_kind"] == "clarify" + assert result["decision_kind"] == "error" assert result["retrieval_result"]["returned_document_ids"] == [] assert result["retrieval_result"]["blocked_reason"] == ( - "clarification required before retrieval policy changes" + "compiler rejected retrieval policy change" ) assert result["prompt_to_user"] == ( f'"{EMPLOYEE_ACCESS}" is currently in use.\n'