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
1 change: 1 addition & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Current generic Python examples include:
- [python/examples/checkpoint_continuation/README.md](/Users/rlippmann/Source/context-compiler-example-integrations/python/examples/checkpoint_continuation/README.md)
- [python/examples/execution_authorization/README.md](/Users/rlippmann/Source/context-compiler-example-integrations/python/examples/execution_authorization/README.md)
- [python/examples/retrieval_filtering/README.md](/Users/rlippmann/Source/context-compiler-example-integrations/python/examples/retrieval_filtering/README.md)
- [python/examples/tool_gating/README.md](/Users/rlippmann/Source/context-compiler-example-integrations/python/examples/tool_gating/README.md)

Reference integrations and optional framework-specific examples remain
secondary to the enforcement point.
36 changes: 28 additions & 8 deletions python/examples/tool_gating/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
# Tool gating

- status: placeholder
- intended enforcement point: tool gating
- intended domain: calendar / email / admin
- intended technology: MCP
- no implementation yet
- examples must use explicit authoritative state
- examples must not derive Context Compiler state from model output
- examples must remain meaningful with an adversarial stub
These examples show the host deciding which tools exist at runtime and which
tool calls can execute.

Context Compiler owns the authoritative policy state.

The host owns the tool registry and tool execution.

Adversarial wording does not expose hidden tools or authorize blocked tools.

## Examples

### `calendar_admin`

Exposes a host-owned `calendar_admin_create_event` tool only when state
contains:

```text
use calendar_admin
```

The host hides and blocks the tool when state is absent or when state contains:

```text
prohibit calendar_admin
```

The tests cover visible-tool changes, execution blocking, adversarial text, and
contradiction / clarification behavior.
34 changes: 34 additions & 0 deletions python/examples/tool_gating/calendar_admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# `calendar_admin`

This example shows host-side tool gating with explicit authoritative Context
Compiler state.

The host owns the tool registry and tool execution.

Context Compiler owns the policy state that decides whether the host exposes
`calendar_admin_create_event`.

The host exposes and allows the calendar admin tool only when state contains:

```text
use calendar_admin
```

The host hides and blocks the calendar admin tool when state is absent or when
state contains:

```text
prohibit calendar_admin
```

Adversarial request text does not enable the tool because the host never derives
authority from user wording or model output.

The tests cover:

- allowed exposure and execution
- absent-state hiding and blocking
- prohibited-state hiding and blocking
- adversarial text that tries to self-authorize
- runtime behavior changing only when authoritative state changes
- contradiction and clarification behavior for conflicting `use` and `prohibit`
1 change: 1 addition & 0 deletions python/examples/tool_gating/calendar_admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Calendar admin tool-gating example."""
195 changes: 195 additions & 0 deletions python/examples/tool_gating/calendar_admin/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""Host-side tool gating using authoritative Context Compiler state."""

from dataclasses import dataclass
from typing import Literal, TypedDict, cast

from context_compiler import (
POLICY_PROHIBIT,
POLICY_USE,
State,
create_engine,
get_decision_state,
get_policy_items,
is_clarify,
)
from context_compiler.engine import Engine


class CalendarToolCall(TypedDict):
tool_name: str
calendar_id: str
event_title: str


class ToolRegistrySnapshot(TypedDict):
available_tools: list[str]
hidden_tools: list[str]


class CalendarToolExecutionResult(TypedDict):
authorization_state: Literal["allowed", "blocked"]
tool_visible: bool
executed: bool
blocked_reason: str | None
tool_result: str | None
registry_snapshot: ToolRegistrySnapshot
execution_log: list[str]


class CalendarToolTurnResult(TypedDict):
decision_kind: Literal["clarify", "update", "passthrough"]
prompt_to_user: str | None
execution_result: CalendarToolExecutionResult


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

kind = decision.get("kind")
kind_name = getattr(kind, "value", None)
if kind_name not in {"clarify", "update", "passthrough"}:
raise ValueError(f"unexpected decision kind: {kind_name}")

return cast(Literal["clarify", "update", "passthrough"], kind_name)


@dataclass
class CalendarAdminHost:
"""Host-owned tool registry and execution layer."""

execution_log: list[str]

def __init__(self) -> None:
self.execution_log = []
self._always_available_tools = ["calendar_view_events"]
self._calendar_admin_tools = ["calendar_admin_create_event"]

def visible_tools(self, state: State) -> ToolRegistrySnapshot:
available_tools = self._always_available_tools.copy()
hidden_tools = self._calendar_admin_tools.copy()

if calendar_admin_tools_are_allowed(state):
available_tools.extend(self._calendar_admin_tools)
hidden_tools = []

return {
"available_tools": available_tools,
"hidden_tools": hidden_tools,
}

def execute_calendar_admin_tool(self, tool_call: CalendarToolCall) -> str:
self.execution_log.append(
f"{tool_call['tool_name']}:{tool_call['calendar_id']}:{tool_call['event_title']}"
)
return (
f"created event '{tool_call['event_title']}' "
f"on calendar '{tool_call['calendar_id']}'"
)


def calendar_admin_tools_are_allowed(state: State) -> bool:
"""Allow calendar admin tools only from authoritative compiler state."""

use_items = set(get_policy_items(state, POLICY_USE))
prohibit_items = set(get_policy_items(state, POLICY_PROHIBIT))

if "calendar_admin" in prohibit_items:
return False

return "calendar_admin" in use_items


def execute_calendar_admin_tool_if_allowed(
tool_call: CalendarToolCall,
*,
state: State,
host: CalendarAdminHost,
) -> CalendarToolExecutionResult:
"""Hide or execute the admin tool based only on authoritative state."""

registry_snapshot = host.visible_tools(state)
tool_visible = tool_call["tool_name"] in registry_snapshot["available_tools"]

if not tool_visible:
return {
"authorization_state": "blocked",
"tool_visible": False,
"executed": False,
"blocked_reason": "calendar_admin state not authorized",
"tool_result": None,
"registry_snapshot": registry_snapshot,
"execution_log": host.execution_log.copy(),
}

tool_result = host.execute_calendar_admin_tool(tool_call)
return {
"authorization_state": "allowed",
"tool_visible": True,
"executed": True,
"blocked_reason": None,
"tool_result": tool_result,
"registry_snapshot": registry_snapshot,
"execution_log": host.execution_log.copy(),
}


def handle_calendar_admin_turn(
engine: Engine,
*,
compiler_input: str,
tool_call: CalendarToolCall,
host: CalendarAdminHost,
) -> CalendarToolTurnResult:
"""Block tool exposure on clarify and otherwise enforce current state."""

decision = engine.step(compiler_input)

if is_clarify(decision):
return {
"decision_kind": "clarify",
"prompt_to_user": decision.get("prompt_to_user"),
"execution_result": {
"authorization_state": "blocked",
"tool_visible": False,
"executed": False,
"blocked_reason": "clarification required before exposing calendar admin tools",
"tool_result": None,
"registry_snapshot": host.visible_tools(engine.state),
"execution_log": host.execution_log.copy(),
},
}

authoritative_state = get_decision_state(decision)
if authoritative_state is None:
authoritative_state = engine.state

return {
"decision_kind": _decision_kind_name(decision),
"prompt_to_user": decision.get("prompt_to_user"),
"execution_result": execute_calendar_admin_tool_if_allowed(
tool_call,
state=authoritative_state,
host=host,
),
}


def run_demo() -> CalendarToolExecutionResult:
"""Run a deterministic demonstration with explicit authorization state."""

engine = create_engine()
engine.step("use calendar_admin")
host = CalendarAdminHost()

return execute_calendar_admin_tool_if_allowed(
{
"tool_name": "calendar_admin_create_event",
"calendar_id": "ops-admin",
"event_title": "Quarterly access review",
},
state=engine.state,
host=host,
)
Loading