Skip to content

Commit 8f17bfc

Browse files
authored
Merge pull request #338 from ai-agent-assembly/v0.0.1/AAASM-4836/prod_mode_governance
[AAASM-4836] 🔒 (examples): Fix production-mode snippet that disabled all governance
2 parents 6e56192 + a8b78d4 commit 8f17bfc

2 files changed

Lines changed: 52 additions & 16 deletions

File tree

python/custom-tool-policy/README.md

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Demonstrates how to add [Agent Assembly](https://github.com/ai-agent-assembly/ex
2727
- Two **allowed** tool calls (`compute_sum`, `fetch_stock_price`).
2828
- Two **denied** tool calls (`send_http_request`, `write_to_disk` — blocked by policy).
2929
- That the wrapped function body **never executes** when governance denies it.
30-
- The `governed()` pattern as the building block for the `GovernedToolRunner` shown in the `llamaindex-tool-policy` example.
30+
- How this `governed()` pattern relates to the framework path: the `llamaindex-tool-policy` example governs tool calls by registering the native `LlamaIndexAdapter` (auto-wired by `init_assembly()`) instead of wrapping each callable.
3131

3232
## Prerequisites
3333

@@ -90,22 +90,51 @@ Running governed tool calls:
9090
uv run pytest tests/ -v
9191
```
9292

93-
## Switching to production mode
94-
95-
Replace `LocalPolicyEngine` with `ctx.client` (the gateway-backed interceptor):
93+
## Production mode — governing real tool calls
94+
95+
`LocalPolicyEngine` is a stand-in for the gateway: it answers the
96+
`check_tool_start` contract (the `GovernanceInterceptor` protocol in
97+
`agent_assembly.adapters`) in-process so this demo runs fully offline. Reaching
98+
the real gateway in production is **not** a matter of passing `ctx.client` to
99+
`governed()`.
100+
101+
> ⚠️ **Do not pass `ctx.client` as the interceptor.** `ctx.client` is a bare
102+
> `GatewayClient` — it has **no** `check_tool_start` method. Wire it into
103+
> `governed()` and `AssemblyCallbackHandler.on_tool_start` finds no check,
104+
> returns without raising, and **allows every tool**: governance is silently
105+
> disabled (fail-open). `ctx.client` is for agent metadata and audit emission,
106+
> not pre-execution policy checks.
107+
108+
**The supported production path is a framework adapter.** Run your tools through
109+
a supported AI framework (LangChain, LlamaIndex, CrewAI, …). On startup
110+
`init_assembly()` auto-detects the installed adapter and wires the real
111+
gateway-backed interceptor into that framework's tool-execution path, so every
112+
tool call is checked against the gateway with no per-tool wrapper and no
113+
interceptor argument of your own. The `llamaindex-tool-policy` example shows
114+
this end to end — it registers the native `LlamaIndexAdapter` and lets
115+
`init_assembly()` supply the live interceptor:
96116

97117
```python
98118
from agent_assembly import init_assembly
99-
from agent_assembly.adapters.langchain import AssemblyCallbackHandler
100-
101-
with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent") as ctx:
102-
from src.policy import governed
103-
tools = {
104-
"compute_sum": governed("compute_sum", compute_sum, ctx.client),
105-
}
106-
result = tools["compute_sum"](a=1, b=2)
119+
120+
# init_assembly() auto-detects the installed framework adapter and wires the
121+
# real gateway-backed interceptor into its tool-execution path. Define tools the
122+
# way the framework expects (e.g. a LlamaIndex FunctionTool); every call is then
123+
# governed against the gateway — no governed() wrapper, no ctx.client.
124+
with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent"):
125+
... # run your framework agent; its tool calls are now governed
107126
```
108127

128+
> **SDK gap.** For the bare no-framework pattern this example teaches, the SDK
129+
> exposes no public drop-in interceptor that talks to a real gateway: the
130+
> gateway-backed interceptor (`RuntimeQueryInterceptor`) is internal and is only
131+
> wired through a registered framework adapter, and the public
132+
> `GovernanceInterceptor` protocol is just the `check_tool_start` contract that
133+
> `LocalPolicyEngine` implements here. To govern plain callables against a real
134+
> gateway today, adopt a supported framework adapter (above), or supply your own
135+
> object that implements `check_tool_start` and answers from the gateway itself
136+
> — never `ctx.client`.
137+
109138
## Troubleshooting
110139

111140
| Problem | Fix |

python/custom-tool-policy/src/policy.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
This example has no framework dependency — only ``agent-assembly``.
44
The policy engine simulates governance with a simple allow/deny rule set.
55
6-
In production:
7-
with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent") as ctx:
8-
# ctx.client is the gateway-backed interceptor; use it in place of
9-
# LocalPolicyEngine below.
6+
``LocalPolicyEngine`` implements the ``check_tool_start`` contract (the public
7+
``GovernanceInterceptor`` protocol in ``agent_assembly.adapters``) in-process so
8+
the demo runs fully offline. It is a stand-in for the gateway, NOT the
9+
production wiring: ``ctx.client`` is a bare ``GatewayClient`` with no
10+
``check_tool_start`` method, so passing it to ``governed()`` makes
11+
``AssemblyCallbackHandler.on_tool_start`` find no check, return without raising,
12+
and allow every tool — governance silently disabled (fail-open). In production
13+
you do not wrap callables against ``ctx.client``; instead run tools through a
14+
supported framework adapter and let ``init_assembly()`` wire the real
15+
gateway-backed interceptor into it. See this example's README "Production mode"
16+
section.
1017
"""
1118
from __future__ import annotations
1219

0 commit comments

Comments
 (0)