Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

custom-tool-policy

Requirement Version
Agent Assembly Python SDK (agent-assembly) >= 0.0.1rc6

Install:

uv add agent-assembly==0.0.1rc6
# or
pip install agent-assembly==0.0.1rc6

The simplest Agent Assembly integration — no AI framework required.

Demonstrates how to add Agent Assembly governance to plain Python functions using the minimal governed() wrapper helper.

What this example demonstrates

  • Initializing Agent Assembly with init_assembly().
  • Wrapping any Python function with governance using governed().
  • Two allowed tool calls (compute_sum, fetch_stock_price).
  • Two denied tool calls (send_http_request, write_to_disk — blocked by policy).
  • That the wrapped function body never executes when governance denies it.
  • 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.

Prerequisites

Requirement Version
Python >= 3.12
uv latest
Agent Assembly Python SDK >= 0.0.1rc6

No API key, no gateway, and no AI framework are required.

Setup

cd python/custom-tool-policy
uv sync --extra dev

Run

uv run python src/main.py

Expected output

==============================================================
  Agent Assembly — Custom Tool Policy Demo
  (no AI framework required)
==============================================================

Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)...
  Agent:    custom-tool-demo-agent
  Gateway:  http://localhost:8080
  Mode:     sdk-only (offline demo)

Policy rules (local simulation of gateway policy):
  DENY   — send_http_request, write_to_disk  (network / disk writes)
  ALLOW  — everything else

Running governed tool calls:
--------------------------------------------
  → compute_sum({'a': 12.5, 'b': 7.3})
     ✅ ALLOWED  — 19.8

  → fetch_stock_price({'ticker': 'AAPL'})
     ✅ ALLOWED  — $211.30 (mock)

  → send_http_request({'url': 'https://example.com/data', 'method': 'POST'})
     ❌ BLOCKED  — Tool 'send_http_request' is blocked by policy rule 'deny_network_and_disk_writes'.

  → write_to_disk({'path': '/etc/cron.d/evil', 'content': 'rm -rf /'})
     ❌ BLOCKED  — Tool 'write_to_disk' is blocked by policy rule 'deny_network_and_disk_writes'.

Run tests

uv run pytest tests/ -v

Production mode — governing real tool calls

LocalPolicyEngine is a stand-in for the gateway: it answers the check_tool_start contract (the GovernanceInterceptor protocol in agent_assembly.adapters) in-process so this demo runs fully offline. Reaching the real gateway in production is not a matter of passing ctx.client to governed().

⚠️ Do not pass ctx.client as the interceptor. ctx.client is a bare GatewayClient — it has no check_tool_start method. Wire it into governed() and AssemblyCallbackHandler.on_tool_start finds no check, returns without raising, and allows every tool: governance is silently disabled (fail-open). ctx.client is for agent metadata and audit emission, not pre-execution policy checks.

The supported production path is a framework adapter. Run your tools through a supported AI framework (LangChain, LlamaIndex, CrewAI, …). On startup init_assembly() auto-detects the installed adapter and wires the real gateway-backed interceptor into that framework's tool-execution path, so every tool call is checked against the gateway with no per-tool wrapper and no interceptor argument of your own. The llamaindex-tool-policy example shows this end to end — it registers the native LlamaIndexAdapter and lets init_assembly() supply the live interceptor:

from agent_assembly import init_assembly

# init_assembly() auto-detects the installed framework adapter and wires the
# real gateway-backed interceptor into its tool-execution path. Define tools the
# way the framework expects (e.g. a LlamaIndex FunctionTool); every call is then
# governed against the gateway — no governed() wrapper, no ctx.client.
with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent"):
    ...  # run your framework agent; its tool calls are now governed

SDK gap. For the bare no-framework pattern this example teaches, the SDK exposes no public drop-in interceptor that talks to a real gateway: the gateway-backed interceptor (RuntimeQueryInterceptor) is internal and is only wired through a registered framework adapter, and the public GovernanceInterceptor protocol is just the check_tool_start contract that LocalPolicyEngine implements here. To govern plain callables against a real gateway today, adopt a supported framework adapter (above), or supply your own object that implements check_tool_start and answers from the gateway itself — never ctx.client.

Troubleshooting

Problem Fix
ModuleNotFoundError: agent_assembly Run uv sync first
ToolExecutionBlockedError in tests Expected — the deny rules for send_http_request and write_to_disk are intentional

Links