From dac6ac246d2f2a156a36471e512762121ea8325f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:00:19 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(docs):=20Mark=20shi?= =?UTF-8?q?pped=20framework=20examples=20as=20validated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guides/framework-examples.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/guides/framework-examples.md b/docs/guides/framework-examples.md index e64b1698..d383d45b 100644 --- a/docs/guides/framework-examples.md +++ b/docs/guides/framework-examples.md @@ -29,18 +29,20 @@ entry points. On exit, all hooks are removed in reverse order. ## Supported frameworks Each framework below ships an adapter under `agent_assembly/adapters/`. The -**Runnable example** column reflects whether a complete, validated example exists in this -repository today. +**Runnable example** column reflects whether a complete, validated example exists today — +either inline in this guide or as a curated script in the central +[`agent-assembly-examples`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python) +repository. | Framework | Adapter | Runnable example | | --- | --- | --- | -| LangChain | `agent_assembly.adapters.langchain` | ✅ Validated — see [Quick start](#langchain-quick-start) below (runs offline against a mock LLM). | -| LangGraph | `agent_assembly.adapters.langgraph` | ⏳ Planned — adapter ships; wraps `StateGraph.compile()`. | -| CrewAI | `agent_assembly.adapters.crewai` | ⏳ Planned — adapter ships. | -| OpenAI Agents | `agent_assembly.adapters.openai_agents` | ⏳ Planned — adapter ships. | -| Pydantic AI | `agent_assembly.adapters.pydantic_ai` | ⏳ Planned — adapter ships. | -| Google ADK | `agent_assembly.adapters.google_adk` | ⏳ Planned — adapter ships. | -| MCP servers | `agent_assembly.adapters.mcp` | ⏳ Planned — adapter ships. | +| LangChain | `agent_assembly.adapters.langchain` | ✅ Validated — see [Quick start](#langchain-quick-start) below (runs offline against a mock LLM), plus [`langchain-basic-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-basic-agent) and [`langchain-research-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-research-agent). | +| LangGraph | `agent_assembly.adapters.langgraph` | ✅ Validated — see [`langgraph`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langgraph) (state-graph governance; wraps `StateGraph.compile()`). | +| CrewAI | `agent_assembly.adapters.crewai` | ✅ Validated — see [`crewai-research-crew`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/crewai-research-crew) (multi-agent crew). | +| OpenAI Agents | `agent_assembly.adapters.openai_agents` | ✅ Validated — see [`openai-agents-sdk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/openai-agents-sdk). | +| Pydantic AI | `agent_assembly.adapters.pydantic_ai` | ✅ Validated — see [`pydantic-ai`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/pydantic-ai). | +| Google ADK | `agent_assembly.adapters.google_adk` | ✅ Validated — see [`google-adk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/google-adk). | +| MCP servers | `agent_assembly.adapters.mcp` | ⏳ Planned — adapter ships; a curated example is not yet vendored. | !!! note "Adapter present vs. example present" A ⏳ row means the **adapter is implemented and registered** — `init_assembly()` will detect From 36c7e1147fb05b4f4ad137d2f4e2c1080e4b715c Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:00:36 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(docs):=20Update=20a?= =?UTF-8?q?dapter-vs-example=20note=20for=20shipped=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guides/framework-examples.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guides/framework-examples.md b/docs/guides/framework-examples.md index d383d45b..3b854db3 100644 --- a/docs/guides/framework-examples.md +++ b/docs/guides/framework-examples.md @@ -45,9 +45,11 @@ repository. | MCP servers | `agent_assembly.adapters.mcp` | ⏳ Planned — adapter ships; a curated example is not yet vendored. | !!! note "Adapter present vs. example present" - A ⏳ row means the **adapter is implemented and registered** — `init_assembly()` will detect - and hook the framework if it's installed — but a curated, end-to-end runnable example is - not yet vendored in `examples/`. Contributions adding one are welcome; see + Every framework above has an **adapter that is implemented and registered** — `init_assembly()` + will detect and hook the framework whenever it's installed, regardless of the example status. + A ✅ row additionally has a curated, end-to-end runnable example you can clone and run. A ⏳ row + (currently only MCP servers) means the adapter ships but a runnable example is not yet vendored. + Contributions adding one are welcome; see [CONTRIBUTING.md](https://github.com/ai-agent-assembly/python-sdk/blob/master/CONTRIBUTING.md). ## LangChain quick start From b2bfc00263717527ea7a971c52cec6f726f5125d Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:00:54 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Link=20central?= =?UTF-8?q?=20agent-assembly-examples=20for=20each=20framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guides/framework-examples.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/guides/framework-examples.md b/docs/guides/framework-examples.md index 3b854db3..7d90da7a 100644 --- a/docs/guides/framework-examples.md +++ b/docs/guides/framework-examples.md @@ -89,4 +89,20 @@ with init_assembly( The same snippet lives in the repository [README](https://github.com/ai-agent-assembly/python-sdk#quick-start) and is the canonical, validated example. See the [`examples/`](https://github.com/ai-agent-assembly/python-sdk/tree/master/examples) -directory for additional runnable scripts and their status. +directory for additional in-repo runnable scripts and their status. + +## More runnable examples + +Curated, end-to-end examples for each framework live in the central +[`agent-assembly-examples`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python) +repository. Each directory is a self-contained, cloneable project: + +- [`langchain-basic-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-basic-agent) — a governed LangChain agent. +- [`langchain-research-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-research-agent) — a governed LangChain research agent. +- [`langgraph`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langgraph) — LangGraph state-graph governance. +- [`crewai-research-crew`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/crewai-research-crew) — a CrewAI multi-agent crew. +- [`openai-agents-sdk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/openai-agents-sdk) — the OpenAI Agents SDK. +- [`pydantic-ai`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/pydantic-ai) — Pydantic AI. +- [`google-adk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/google-adk) — Google ADK. +- [`llamaindex-tool-policy`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/llamaindex-tool-policy) — LlamaIndex tool policy. +- [`custom-tool-policy`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/custom-tool-policy) — framework-free tool governance. From 5146fe3a3f9ae26cf0af83d275cbc4b9bba8cf4c Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:24 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20LangChain=20basic=20agent=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/langchain-basic-agent.md | 194 +++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 docs/examples/langchain-basic-agent.md diff --git a/docs/examples/langchain-basic-agent.md b/docs/examples/langchain-basic-agent.md new file mode 100644 index 00000000..eb5d173c --- /dev/null +++ b/docs/examples/langchain-basic-agent.md @@ -0,0 +1,194 @@ +# LangChain — basic agent + +Integrate Agent Assembly with LangChain to enforce governance policy on tool calls before they execute. + +## What this example demonstrates + +This example wires Agent Assembly governance into a LangChain agent so that every tool call is checked against policy *before* the tool runs. It covers: + +- Initializing Agent Assembly with `init_assembly()`. +- Wrapping LangChain tools with `AssemblyCallbackHandler` + a governance interceptor. +- Running an **allowed** tool call (`get_weather`). +- Running a **denied** tool call (`delete_files` — blocked by a policy rule). +- Running a **pending** tool call (`send_email` — requires human approval; auto-denied in offline mode). +- How `ToolExecutionBlockedError` is raised when a tool is blocked. + +The governance point: a safe tool is allowed, a destructive tool is denied, and a tool that requires human approval is held pending — and because the demo runs offline with no approver available, the pending call is auto-denied. + +## The framework / library + +This example uses [LangChain](https://python.langchain.com/), specifically its `langchain_core.tools` `@tool` decorator and the callback-handler hook surface. + +Dependency version pins from `pyproject.toml`: + +| Dependency | Pin | +|---|---| +| `agent-assembly` | `>=0.0.1a2` | +| `langchain-core` | `>=0.2.0` | +| `pytest` (dev) | `>=8.0.0` | +| `pytest-mock` (dev) | `>=3.14.0` | + +Python requirement: `>=3.12`. + +## How it works + +The adapter flow ties LangChain's tool-call lifecycle to Agent Assembly policy: + +1. **`init_assembly()`** creates an Agent Assembly context. In this example it is called with `gateway_url`, `api_key`, `agent_id="langchain-demo-agent"`, and `mode="sdk-only"`, and is used as a context manager so the assembly context shuts down cleanly on exit. +2. **`AssemblyCallbackHandler`** (from `agent_assembly.adapters.langchain`) hooks LangChain tool calls. It is constructed with an `interceptor` — here `LocalPolicyEngine` — and its `on_tool_start(...)` is invoked before each tool runs. +3. **The interceptor decides** allow / deny / pending. `LocalPolicyEngine.check_tool_start()` returns a decision dict that mirrors the gateway wire format: + - `delete_files` / `write_file` → `{"status": "deny", ...}` + - `send_email` → `{"status": "pending", ...}` + - everything else → `{"status": "allow"}` +4. **Pending resolution**: when a tool is pending, the handler consults `wait_for_tool_approval()`. In offline mode no approver is available, so that method returns a `deny` decision. +5. **A blocked call surfaces as `ToolExecutionBlockedError`.** When the decision is deny (or an unresolved pending), the handler raises `ToolExecutionBlockedError`; `main.py` catches it and prints the block reason. Only an `allow` decision lets the LangChain tool actually `invoke`. + +In production, policy is enforced by the gateway server-side; `LocalPolicyEngine` is a local simulation of that governance layer so the demo runs without a running gateway. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +This example runs fully offline — **no running Agent Assembly gateway is required** for the demo. + +Example-specific requirements: + +| Requirement | Version | +|---|---| +| Python | `>= 3.12` | +| [uv](https://github.com/astral-sh/uv) | latest | +| Agent Assembly Python SDK | `>= 0.0.1a2` | + +Setup and run: + +```bash +cd python/langchain-basic-agent +uv sync --extra dev +uv run python src/main.py +``` + +### Production mode (optional) + +To run against a real gateway or SaaS workspace, copy `.env.example` to `.env`, fill in credentials, and pass the gateway environment variables. The relevant variables from `.env.example` are: + +```bash +AGENT_ASSEMBLY_GATEWAY_URL=http://localhost:8080 \ +AGENT_ASSEMBLY_API_KEY=your-key \ +uv run python src/main.py +``` + +`.env.example` also exposes an optional `OPENAI_API_KEY` for a LangChain LLM provider — the example runs with mock tools by default, so it is not required. In production, remove the `mode="sdk-only"` argument from `init_assembly()` and replace `LocalPolicyEngine` with the gateway-backed interceptor; the SDK then enforces the policy rules configured in the gateway automatically. + +## Code walkthrough + +**Tools** (`src/tools.py`) — three LangChain `@tool` functions, one per policy outcome: + +```python +@tool +def get_weather(city: str) -> str: + """Get the current weather for a city. + + Returns mock data in offline / demo mode. + """ + return f"🌤 Weather in {city}: 22°C, partly cloudy (mock response)" +``` + +`get_weather` is the safe, allowed tool. `delete_files` (destructive, denied) and `send_email` (requires approval) are defined alongside it. + +**Policy** (`src/policy.py`) — `DENIED_TOOLS` and `PENDING_TOOLS` are the local rule sets that the interceptor checks against: + +```python +DENIED_TOOLS: frozenset[str] = frozenset({ + "delete_files", + "write_file", +}) + +PENDING_TOOLS: frozenset[str] = frozenset({ + "send_email", +}) +``` + +`check_tool_start()` maps a tool name to an allow / deny / pending decision: + +```python +def check_tool_start(self, serialized, input_str, run_id=None, **kwargs): + tool_name = serialized.get("name", "") + if tool_name in DENIED_TOOLS: + return {"status": "deny", "reason": ( + f"Tool '{tool_name}' is blocked by policy rule " + "'deny_destructive_operations'.")} + if tool_name in PENDING_TOOLS: + return {"status": "pending", + "reason": f"Tool '{tool_name}' requires human approval before execution."} + return {"status": "allow"} +``` + +For pending tools, `wait_for_tool_approval()` returns a `deny` because no approver exists in offline mode. + +**Governed call flow** (`src/main.py`) — each demo call routes through `on_tool_start` before the tool is invoked, and a block is caught as `ToolExecutionBlockedError`: + +```python +try: + handler.on_tool_start( + serialized={"name": tool_name, "type": "tool"}, + input_str=input_str, + run_id=run_id, + ) + tool_map = {t.name: t for t in _TOOLS} + result = tool_map[tool_name].invoke(input_str) + print(f" ✅ ALLOWED — {result}") +except ToolExecutionBlockedError as exc: + print(f" ❌ BLOCKED — {exc}") +``` + +The handler is constructed with the policy engine as its interceptor: `handler = AssemblyCallbackHandler(interceptor=policy)`. + +## Notes & caveats + +!!! note "Offline / sdk-only mode" + The demo runs with `mode="sdk-only"` and requires no running gateway. `LocalPolicyEngine` simulates the gateway's policy enforcement locally; in production the gateway enforces policy server-side and the SDK applies it automatically. + +!!! warning "Pending tools are auto-denied offline" + `send_email` is a pending (approval-required) tool. In offline mode there is no approver, so `wait_for_tool_approval()` returns a deny decision and the call is blocked. + +!!! tip "Troubleshooting" + - `ModuleNotFoundError: agent_assembly` → run `uv sync` first. + - `ModuleNotFoundError: langchain_core` → run `uv sync` — `langchain-core` is a required dependency. + - `ToolExecutionBlockedError` in tests → expected; the deny/pending policy rules are intentional. + +## Expected behavior + +``` +============================================================== + Agent Assembly — LangChain Basic Agent Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: langchain-demo-agent + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — delete_files, write_file (destructive operations) + PENDING — send_email (requires human approval) + ALLOW — everything else + +Running governed tool calls: +-------------------------------------------- + → get_weather({"city": "London"}) + ✅ ALLOWED — 🌤 Weather in {"city": "London"}: 22°C, partly cloudy (mock response) + + → delete_files({"path": "/etc/passwd"}) + ❌ BLOCKED — Tool 'delete_files' is blocked by policy rule 'deny_destructive_operations'. + + → send_email({"to": "all@company.com", "subject": "Hello", "body": "World"}) + ❌ BLOCKED — Tool 'send_email' requires approval, but no approver is available in offline mode. + +Assembly context shut down. +``` + +## Links + +- Example directory: +- Example README: +- LangChain documentation: From d591f8cdcfd4879b422e2d44c82b77204a4db457 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20LangChain=20research=20agent=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/langchain-research-agent.md | 216 ++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 docs/examples/langchain-research-agent.md diff --git a/docs/examples/langchain-research-agent.md b/docs/examples/langchain-research-agent.md new file mode 100644 index 00000000..ad94fa2e --- /dev/null +++ b/docs/examples/langchain-research-agent.md @@ -0,0 +1,216 @@ +# LangChain — research agent + +A richer ReAct variant of the basic LangChain example, governing a web-search-and-calculator research agent under a single *balanced* policy. + +## What this example demonstrates + +This example initializes Agent Assembly with `init_assembly()` in `sdk-only` mode and runs a ReAct-style research trajectory over two tools — `web_search` and `calculator` — under a **balanced policy** that bundles four controls at once: + +- **Network allowlist** — outbound egress is only allowed to `*.openai.com`. +- **Daily budget** — tool calls are metered against a `$1.00 / day` cap. +- **Tool-call logging** — every governed call is recorded as an audit event. +- **Credential-leak block** — any tool input carrying a secret is denied. + +It also includes a credential-leak demo that uses a **SAFE, FAKE** key (`sk-FAKE...`) — never a real secret — to show the leak rule firing. Finally, `--mock` mode runs the whole demo offline with no API keys, so CI can run it. + +## The framework / library + +This example is built on [LangChain](https://python.langchain.com/). The tools are defined with LangChain's `@tool` decorator (`langchain_core.tools.tool`), and the governance layer hooks into LangChain via Agent Assembly's `AssemblyCallbackHandler` callback handler. + +Version pins from `pyproject.toml`: + +| Dependency | Version | +|---|---| +| `agent-assembly` | `>=0.0.1a2` | +| `langchain-core` | `>=0.2.0` | +| Python | `>=3.12` | + +Dev extras pin `pytest>=8.0.0` and `pytest-mock>=3.14.0`. + +## How it works + +`main.py` opens an Agent Assembly context with `init_assembly()` in `sdk-only` mode: + +```python +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="langchain-research-agent", + mode="sdk-only", +) as ctx: + ... +``` + +Inside the context it constructs a `BalancedPolicyEngine` and wires it into LangChain through `AssemblyCallbackHandler(interceptor=policy)`. + +The agent then replays a scripted **ReAct trajectory** — the steps a real LLM-driven agent would emit while researching its question — over the two tools `web_search` and `calculator`. Each step goes through `_run_governed_call`, which fires the handler's `on_tool_start` callback before invoking the tool. The `BalancedPolicyEngine.check_tool_start` method evaluates the rules in priority order: + +1. **Credential-leak block** (highest priority, fail-closed) — if the input matches any credential pattern (e.g. `sk-...`, `AKIA...`, `api_key=...`, `secret/password/token=...`), the call is denied. +2. **Network allowlist** — any `http(s)://` URL in the input has its host extracted and checked against `NETWORK_ALLOWLIST` (`*.openai.com`, `openai.com`); a non-allowlisted host is denied. +3. **Daily budget** — the per-call cost (`web_search` = `$0.02`, `calculator` = `$0.00`) is checked against the remaining `$1.00 / day` cap; an exhausted budget denies the call. +4. **Tool-call logging** — on an allow, the budget is charged and the decision is recorded. + +Every call, allowed or denied, is appended to `audit_log`. A denied call surfaces as a `ToolExecutionBlockedError` raised by the callback handler, which `_run_governed_call` catches and prints as `❌ BLOCKED`. At the end of the run the demo replays the full audit trail and the final budget total. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then, from the examples repository: + +```bash +cd python/langchain-research-agent +uv sync --extra dev +uv run python src/main.py --mock +``` + +`--mock` replays a scripted ReAct trajectory offline with no API keys. The example also auto-falls back to mock mode whenever `OPENAI_API_KEY` is unset. + +## Code walkthrough + +The balanced policy configuration (`policy.py`): + +```python +#: Outbound network egress is only permitted to hosts matching this allowlist. +NETWORK_ALLOWLIST: tuple[str, ...] = ("*.openai.com", "openai.com") + +#: Per-day spend ceiling, in USD. Tool calls are metered against this cap. +DAILY_BUDGET_USD: float = 1.00 + +#: Per-call cost model (USD) used to meter spend in offline mode. +TOOL_COSTS: dict[str, float] = { + "web_search": 0.02, + "calculator": 0.00, +} +``` + +The rule evaluation order in `check_tool_start` — credential-leak first, then allowlist, then budget (`policy.py`): + +```python +# 4. Credential-leak block — highest priority, fail closed. +if _contains_credential(input_str): + reason = ( + f"Tool '{tool_name}' input contains a credential and is blocked " + "by policy rule 'block_credential_leak'." + ) + self._record(tool_name, "deny", reason) + return {"status": "deny", "reason": reason} + +# 1. Network allowlist — deny egress to non-allowlisted hosts. +match = re.search(r"https?://[^\s\"']+", input_str) +if match: + host = _extract_host(match.group(0)) + if not _host_allowed(host): + reason = ( + f"Tool '{tool_name}' attempted egress to '{host}', which is " + "not on the network allowlist (*.openai.com)." + ) + self._record(tool_name, "deny", reason) + return {"status": "deny", "reason": reason} +``` + +The scripted ReAct trajectory replayed in `--mock` mode (`main.py`): + +```python +_MOCK_REACT_STEPS: list[tuple[str, str]] = [ + ("web_search", '{"query": "speed of light"}'), + ("web_search", '{"query": "population of France"}'), + ("calculator", '{"expression": "299792458 / 68000000"}'), + # An egress attempt to a host NOT on the allowlist — blocked by policy. + ("web_search", '{"query": "fetch https://evil-exfil.example.com/leak"}'), +] +``` + +The governed call: it fires the callback before invoking the tool and catches a block (`main.py`): + +```python +def _run_governed_call(handler, tool_name, input_str): + print(f" → {tool_name}({input_str})") + try: + handler.on_tool_start( + serialized={"name": tool_name, "type": "tool"}, + input_str=input_str, + run_id=uuid4(), + ) + result = _TOOLS[tool_name].invoke(json.loads(input_str)) + print(f" ✅ ALLOWED — {result}") + except ToolExecutionBlockedError as exc: + print(f" ❌ BLOCKED — {exc}") + print() +``` + +## Notes & caveats + +!!! warning "The credential-leak demo uses a SAFE FAKE key" + The credential-leak step submits `sk-FAKE0000DEMO0000NOTAREALKEY0000` — a deliberately fake, non-functional key. It is used only to show the credential-leak policy rule firing, and is never a real secret. + +!!! tip "`--mock` runs offline" + `--mock` replays the scripted ReAct trajectory offline with no API keys and no real network access, which is what CI runs. The example also auto-falls back to mock mode whenever `OPENAI_API_KEY` is unset. + +## Expected behavior + +``` +================================================================ + Agent Assembly — LangChain ReAct Research Agent +================================================================ + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: langchain-research-agent + Gateway: http://localhost:8080 + Mode: sdk-only (mock (offline)) + +Balanced policy (local simulation of gateway policy): + ALLOWLIST — outbound egress to *.openai.com, openai.com + BUDGET — $1.00 / day, metered per tool call + LOG — every tool call recorded as an audit event + BLOCK — any tool input that leaks a credential + +Running ReAct research trajectory: +---------------------------------------------- + → web_search({"query": "speed of light"}) + ✅ ALLOWED — The speed of light in vacuum is 299792458 metres per second. + + → web_search({"query": "population of France"}) + ✅ ALLOWED — France has a population of approximately 68000000 people. + + → calculator({"expression": "299792458 / 68000000"}) + ✅ ALLOWED — 299792458 / 68000000 = 4.40871 + + → web_search({"query": "fetch https://evil-exfil.example.com/leak"}) + ❌ BLOCKED — Tool 'web_search' attempted egress to 'evil-exfil.example.com', which is not on the network allowlist (*.openai.com). + +Credential-leak demo (SAFE FAKE key): +---------------------------------------------- + → web_search({"query": "summarize using api_key=sk-FAKE0000DEMO0000NOTAREALKEY0000"}) + ❌ BLOCKED — Tool 'web_search' input contains a credential and is blocked by policy rule 'block_credential_leak'. + +Governance events recorded this run: +---------------------------------------------- + ✅ web_search allow — allowed (charged $0.02; spent=$0.02 / limit=$1.00 (2%)) + ✅ web_search allow — allowed (charged $0.02; spent=$0.04 / limit=$1.00 (4%)) + ✅ calculator allow — allowed (charged $0.00; spent=$0.04 / limit=$1.00 (4%)) + ❌ web_search deny — Tool 'web_search' attempted egress to 'evil-exfil.example.com', which is not on the network allowlist (*.openai.com). + ❌ web_search deny — Tool 'web_search' input contains a credential and is blocked by policy rule 'block_credential_leak'. + +Final budget: spent=$0.04 / limit=$1.00 (4%) + +Assembly context shut down. +``` + +### How to read the governance events + +| Event | Governance control | Outcome | +|---|---|---| +| `web_search("speed of light")` | tool-call capture + budget | **ALLOWED**, charged `$0.02` | +| `web_search("population of France")` | tool-call capture + budget | **ALLOWED**, charged `$0.02` | +| `calculator(...)` | tool-call capture + budget | **ALLOWED**, `$0.00` (local compute) | +| `web_search(... evil-exfil.example.com ...)` | network allowlist | **BLOCKED** — host not on `*.openai.com` | +| `web_search(... api_key=sk-FAKE... )` | credential-leak block | **BLOCKED** — secret detected in input | + +The final block replays the full audit trail and the running budget total — the governance evidence a real gateway would persist server-side. + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-research-agent) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/langchain-research-agent/README.md) +- [LangChain documentation](https://python.langchain.com/) From 65e45212fe6c4d1b182fb35677d8fdd504104167 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20LangGraph=20node-level=20governance=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/langgraph.md | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/examples/langgraph.md diff --git a/docs/examples/langgraph.md b/docs/examples/langgraph.md new file mode 100644 index 00000000..f778294a --- /dev/null +++ b/docs/examples/langgraph.md @@ -0,0 +1,170 @@ +# LangGraph — node-level governance + +Enforce Agent Assembly policy on the tools a LangGraph graph's nodes call — before any tool executes — by wrapping a compiled `StateGraph` with node-level governance hooks. + +## What this example demonstrates + +- Initializing Agent Assembly with `init_assembly()` in offline `sdk-only` mode. +- Installing **node-level** governance hooks with `LangGraphAdapter`, which wraps a compiled `StateGraph`'s nodes. +- Routing tool calls through LangChain's `AssemblyCallbackHandler` so each tool is checked against policy. +- A linear graph `START → research → report → END` where: + - `research` calls an **allowed** tool (`get_weather`). + - `report` calls a **denied** tool (`delete_files` — blocked by policy). +- How `ToolExecutionBlockedError` halts the graph the moment a blocked tool is reached. + +## The framework / library + +[LangGraph](https://langchain-ai.github.io/langgraph/) builds stateful, graph-structured agent workflows. This example pins the following versions (from `pyproject.toml`): + +| Dependency | Version | +|---|---| +| `agent-assembly` | `>= 0.0.1a2` | +| `langgraph` | `>= 0.2.0` | +| `langchain-core` | `>= 0.2.0` | +| Python | `>= 3.12` | + +Dev extras (`--extra dev`): `pytest>=8.0.0`, `pytest-mock>=3.14.0`. + +## How it works + +`main.py` opens an Agent Assembly context with `init_assembly()` in `sdk-only` mode — fully offline, no gateway or LLM required: + +```python +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="langgraph-demo-agent", + mode="sdk-only", +) as ctx: + ... +``` + +The gateway URL defaults to `http://localhost:8080`, read from `AGENT_ASSEMBLY_GATEWAY_URL`; `AGENT_ASSEMBLY_API_KEY` supplies the optional API key. Both are unset in the offline demo. + +Inside the context, a `LocalPolicyEngine` (offline stand-in for gateway-side policy) is wired to an `AssemblyCallbackHandler`, and a `LangGraphAdapter` installs the node-level hooks: + +```python +policy = LocalPolicyEngine() +handler = AssemblyCallbackHandler(interceptor=policy) + +adapter = LangGraphAdapter() +adapter.set_process_agent_id(ctx.client.agent_id) +adapter.register_hooks(handler) +``` + +The adapter wraps the compiled graph's nodes so tool calls inside each node are governed. When the graph is invoked, each node calls a tool through the handler. `LocalPolicyEngine.check_tool_start` returns an allow / deny / pending decision; the handler raises `ToolExecutionBlockedError` for deny (and unresolved-pending) outcomes. The `research` node's `get_weather` is allowed, but the `report` node's `delete_files` is denied — so the graph halts mid-execution at the `report` node. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then: + +```bash +cd python/langgraph +uv sync --extra dev +uv run python src/main.py +``` + +The example runs **fully offline**: the graph is driven with deterministic nodes, so no LLM, API key, or running gateway is required. + +## Code walkthrough + +The graph is a linear two-node `StateGraph`, compiled and returned (`graph.py`): + +```python +def build_graph(handler: AssemblyCallbackHandler) -> object: + from langgraph.graph import END, START, StateGraph + + graph = StateGraph(GraphState) + graph.add_node("research", lambda state: _research_node(handler, state)) + graph.add_node("report", lambda state: _report_node(handler, state)) + graph.add_edge(START, "research") + graph.add_edge("research", "report") + graph.add_edge("report", END) + return graph.compile() +``` + +Each node fires a tool call through the handler. The `report` node calls the denied `delete_files` tool (`graph.py`): + +```python +def _report_node(handler: AssemblyCallbackHandler, state: GraphState) -> GraphState: + """Destructive node: calls the denied ``delete_files`` tool.""" + handler.on_tool_start( + serialized={"name": "delete_files"}, + input_str='{"path": "/etc/passwd"}', + run_id=uuid4(), + ) + return {**state, "output": "deleted (should not be reached)"} +``` + +The local policy engine returns the deny decision the handler acts on (`policy.py`): + +```python +def check_tool_start(self, serialized, input_str, run_id=None, **kwargs): + tool_name = serialized.get("name", "") + if tool_name in DENIED_TOOLS: + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' is blocked by policy rule " + "'deny_destructive_operations'." + ), + } + if tool_name in PENDING_TOOLS: + return { + "status": "pending", + "reason": f"Tool '{tool_name}' requires human approval before execution.", + } + return {"status": "allow"} +``` + +`DENIED_TOOLS` is `{"delete_files", "write_file"}` and `PENDING_TOOLS` is `{"send_email"}`; everything else is allowed. + +## Notes & caveats + +!!! note + The example runs fully offline. The graph is driven with deterministic nodes, so no LLM, API key, or running gateway is required, and no Agent Assembly gateway is needed for the demo. In offline mode, `wait_for_tool_approval` denies pending tools because no approver is available. + +!!! tip + Troubleshooting (from the README): + + - `ModuleNotFoundError: agent_assembly` → run `uv sync` first. + - `ModuleNotFoundError: langgraph` → run `uv sync` — `langgraph` is a required dependency. + - `ToolExecutionBlockedError` in tests → expected; the deny/pending policy rules are intentional. + +!!! note "Switching to production mode" + Start an Agent Assembly gateway (or use your SaaS workspace URL), copy `.env.example` to `.env`, and run with `AGENT_ASSEMBLY_GATEWAY_URL` / `AGENT_ASSEMBLY_API_KEY` set. In production, `init_assembly()` auto-detects LangGraph and registers the adapter automatically, the gateway enforces the policy rules, and `LocalPolicyEngine` is replaced with the gateway-backed interceptor. + +## Expected behavior + +``` +============================================================== + Agent Assembly — LangGraph Governed Agent Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: langgraph-demo-agent + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — delete_files, write_file (destructive operations) + PENDING — send_email (requires human approval) + ALLOW — everything else + +Invoking governed graph: START → research → report → END +-------------------------------------------- + → research node: get_weather + ✅ ALLOWED — gathered notes (mock response) + → report node: delete_files + ❌ BLOCKED — Tool 'delete_files' is blocked by policy rule 'deny_destructive_operations'. + +Assembly context shut down. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langgraph) +- [README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/langgraph/README.md) +- [LangGraph documentation](https://langchain-ai.github.io/langgraph/) From ccb0436121478d758da7acdee530131aeec50ab0 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20CrewAI=20research=20crew=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/crewai-research-crew.md | 209 ++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 docs/examples/crewai-research-crew.md diff --git a/docs/examples/crewai-research-crew.md b/docs/examples/crewai-research-crew.md new file mode 100644 index 00000000..99c19e68 --- /dev/null +++ b/docs/examples/crewai-research-crew.md @@ -0,0 +1,209 @@ +# CrewAI — multi-agent research crew + +A three-agent CrewAI-style research crew (researcher → writer → critic) governed by Agent Assembly, where every governed tool call is attributed to the acting agent with the full delegation chain captured on each audit event. + +## What this example demonstrates + +- A three-agent crew: **researcher → writer → critic**, each with a distinct role. +- **Agent-delegation tracking** — every governed call records an `AuditEvent` whose `call_stack` is the delegation chain (`parent → agent → tool`), built from the SDK's real `agent_assembly.types.AuditEvent` and `CallStackNode`. +- **Multi-agent governance** under one policy: + - **File-write approval** — any agent that attempts `write_file` is gated; the decision is `pending` until an approver signs off (rejected in this demo). + - **Shared daily budget** — tool calls across all three agents are metered against a single `$2.00 / day` cap. +- `--mock` mode: the whole crew runs offline with **no `crewai` install and no API keys**, so CI can run it. + +## The framework / library + +This example governs a [CrewAI](https://docs.crewai.com/)-style multi-agent crew. + +Dependency pins from `pyproject.toml`: + +- `agent-assembly>=0.0.1a2` — the Agent Assembly Python SDK (always required). +- The optional `live` extra pulls in `crewai>=0.30.0` — needed only for the real-crew integration. The `--mock` demo (what CI runs) needs none of it; it replays the crew's delegation trajectory offline. +- The `dev` extra provides `pytest>=8.0.0` and `pytest-mock>=3.14.0`. + +The package requires Python `>=3.12`. + +## How it works + +`main()` initializes the SDK with `init_assembly(...)` in `mode="sdk-only"`, passing `agent_id="crewai-research-crew"` and a `gateway_url` that defaults to `http://localhost:8080`. The returned context manager exposes `ctx.client` and `ctx.network_mode`. + +Governance is simulated locally by `CrewPolicyEngine` (from `src/policy.py`), wired into the SDK through `AssemblyCallbackHandler(interceptor=policy)`. The crew is described in `src/crew.py` as three `CrewMember` dataclasses, and the offline run replays a scripted `MOCK_TRAJECTORY` of `CrewStep`s. For each step, `main()` calls `policy.acting_as(agent, parent)` to set the active crew member, then fires `handler.on_tool_start(...)`. + +`CrewPolicyEngine` applies the same policy to every agent's tool calls: + +- **File-write approval gate.** `check_tool_start` returns `status="pending"` for any tool in `APPROVAL_REQUIRED_TOOLS` (`{"write_file"}`), deferring to `wait_for_tool_approval`. There, `MockApprover.decide(...)` returns its `auto_approve` value — `False` in the demo — so the decision becomes `deny` with the message that the crew may not persist files without sign-off. +- **Shared daily budget.** Non-approval tools are priced from `TOOL_COSTS` (defaulting to `$0.01`) and charged against one `BudgetTracker` shared across all three agents; if the cap is exhausted the call is denied. +- **Delegation call stack.** Every allow/deny call is recorded by `_emit(...)`, which constructs a `CallStackNode` chain `parent → acting agent → tool` and appends an `AuditEvent` (carrying `call_stack` plus `crew_member` / `delegated_by` labels) to `policy.audit_events`. + +After the trajectory, `main()` prints each recorded `AuditEvent` (decision, action type, and the flattened delegation chain) and the final shared budget via `policy.budget.status()`. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then, from the example directory: + +```bash +cd python/crewai-research-crew +uv sync --extra dev +uv run python src/main.py --mock +``` + +`--mock` replays the scripted crew delegation trajectory offline — no gateway, no `crewai`, and no API keys. The example also auto-falls back to mock mode whenever `OPENAI_API_KEY` is unset. + +To drive the real CrewAI crew instead, install the optional `live` extra: + +```bash +pip install -e '.[live]' +``` + +## Code walkthrough + +The shared budget, approval gate, and required-approval tool set are declared at module scope in `src/policy.py`: + +```python +#: Shared per-day spend ceiling (USD) across every agent in the crew. +DAILY_BUDGET_USD: float = 2.00 + +#: Per-call cost model (USD) used to meter spend in offline mode. +TOOL_COSTS: dict[str, float] = { + "web_search": 0.05, + "compose_report": 0.10, + "review_text": 0.05, + "write_file": 0.00, +} + +#: Tools that require human approval before execution. +APPROVAL_REQUIRED_TOOLS: frozenset[str] = frozenset({"write_file"}) +``` + +`check_tool_start` routes a `write_file` to the approval path and meters everything else against the shared budget: + +```python +# 1. File-write approval gate — defer to wait_for_tool_approval. +if tool_name in APPROVAL_REQUIRED_TOOLS: + return {"status": "pending", "reason": (...)} + +# 2. Shared daily budget — deny once the crew's cap is exhausted. +cost = TOOL_COSTS.get(tool_name, 0.01) +if not self.budget.can_afford(cost): + self._emit(tool_name, "deny") + return {"status": "deny", "reason": (...)} + +self.budget.charge(cost) +self._emit(tool_name, "allow") +``` + +Each governed call records an `AuditEvent` whose `call_stack` is the delegation chain: + +```python +tool_node = CallStackNode(id=str(uuid4()), kind="tool", label=tool_name) +acting_node = CallStackNode( + id=str(uuid4()), kind="llm", label=self._acting_agent, children=[tool_node] +) +if self._parent_agent is not None: + stack = [CallStackNode(id=str(uuid4()), kind="llm", + label=self._parent_agent, children=[acting_node])] +else: + stack = [acting_node] +``` + +The crew members and their scripted delegation trajectory live in `src/crew.py`: + +```python +CREW: tuple[CrewMember, ...] = (RESEARCHER, WRITER, CRITIC) + +MOCK_TRAJECTORY: tuple[CrewStep, ...] = ( + CrewStep("researcher", None, "web_search", {"query": "agent governance"}), + CrewStep("researcher", None, "web_search", {"query": "interception layers"}), + CrewStep("writer", "researcher", "compose_report", {"section": "summary"}), + CrewStep("critic", "writer", "review_text", {"target": "summary"}), + # The critic tries to persist the report — file writes require approval. + CrewStep("critic", "writer", "write_file", {"path": "report.md"}), +) +``` + +## Notes & caveats + +!!! tip "Mock mode needs no `crewai` and no API keys" + The `--mock` path replays the crew's delegation trajectory entirely offline — no gateway, no `crewai` install, and no LLM provider key — which is exactly what makes it safe to run in CI. + +!!! note "Seeing the approval path succeed" + `MockApprover` rejects file writes by default (`auto_approve=False`), so the demo shows the `write_file` request denied. To see the approval path succeed instead, construct the policy with an auto-approving approver — `MockApprover(auto_approve=True)` — and the `write_file` event then records an `allow` decision. + +## Expected behavior + +Running `uv run python src/main.py --mock` produces: + +``` +================================================================ + Agent Assembly — CrewAI Multi-Agent Research Crew +================================================================ + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: crewai-research-crew + Gateway: http://localhost:8080 + Mode: sdk-only (mock (offline)) + +Crew members: + • researcher — Senior Research Analyst + • writer — Technical Writer + • critic — Editorial Critic + +Crew policy (local simulation of gateway policy): + APPROVAL — any agent attempting a file write must be approved + BUDGET — $2.00 / day, shared across all agents + TRACK — every call recorded with its delegation call stack + +Running crew delegation trajectory: +---------------------------------------------- + [researcher] (crew entry agent) + → web_search({"query": "agent governance"}) + ✅ ALLOWED + + [researcher] (crew entry agent) + → web_search({"query": "interception layers"}) + ✅ ALLOWED + + [writer] (delegated by researcher) + → compose_report({"section": "summary"}) + ✅ ALLOWED + + [critic] (delegated by writer) + → review_text({"target": "summary"}) + ✅ ALLOWED + + [critic] (delegated by writer) + → write_file({"path": "report.md"}) + ❌ BLOCKED — Approval for 'write_file' by 'critic' was rejected — the crew may not persist files without sign-off. + +Delegation-aware audit events recorded this run: +---------------------------------------------- + ✅ allow web_search chain: researcher → web_search + ✅ allow web_search chain: researcher → web_search + ✅ allow compose_report chain: researcher → writer → compose_report + ✅ allow review_text chain: writer → critic → review_text + ❌ deny write_file chain: writer → critic → write_file + +Final crew budget: spent=$0.25 / limit=$2.00 (12%) + +Assembly context shut down. +``` + +Governance-output walkthrough: + +| Step | Acting agent | Delegated by | Governance control | Outcome | +|---|---|---|---|---| +| `web_search` | researcher | — (entry) | shared budget | **ALLOWED**, `$0.05` | +| `web_search` | researcher | — (entry) | shared budget | **ALLOWED**, `$0.05` | +| `compose_report` | writer | researcher | shared budget | **ALLOWED**, `$0.10` | +| `review_text` | critic | writer | shared budget | **ALLOWED**, `$0.05` | +| `write_file` | critic | writer | file-write approval | **BLOCKED** — approval rejected | + +The `chain:` column in the audit replay is the delegation call stack each `AuditEvent` carries: it shows which agent delegated to which, down to the tool. This is the agent-delegation tracking that distinguishes multi-agent governance from single-agent governance — a real gateway persists the same call stack so an operator can see exactly who delegated a blocked action. + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/crewai-research-crew) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/crewai-research-crew/README.md) +- [CrewAI documentation](https://docs.crewai.com/) From 60d32228c44cce2f4750c82ae6f7adbf75fa266f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20OpenAI=20Agents=20SDK=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/openai-agents-sdk.md | 162 +++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/examples/openai-agents-sdk.md diff --git a/docs/examples/openai-agents-sdk.md b/docs/examples/openai-agents-sdk.md new file mode 100644 index 00000000..c42c43e3 --- /dev/null +++ b/docs/examples/openai-agents-sdk.md @@ -0,0 +1,162 @@ +# OpenAI Agents SDK + +This example integrates Agent Assembly with the OpenAI Agents SDK to enforce governance policy — including approval gates — on tool calls before they execute. + +## What this example demonstrates + +- Initializing Agent Assembly with `init_assembly()`. +- Enforcing a governance policy using `AssemblyCallbackHandler` plus a policy interceptor. +- Running an **allowed** tool call (`search_documents`). +- Running a tool that requires **human approval** (`send_message_to_user` — auto-denied offline). +- Running a **denied** tool call (`delete_record` — blocked by a policy rule). +- How the `OpenAIAgentsPatch` intercepts `FunctionTool.__call__` at the framework level. + +## The framework / library + +This example is built on the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/). + +Version pins (from `pyproject.toml`): + +| Dependency | Constraint | +|---|---| +| Python | `>=3.12` | +| `agent-assembly` | `>=0.0.1a2` | +| `openai-agents` | `>=0.0.3` | + +Dev extras: `pytest>=8.0.0`, `pytest-mock>=3.14.0`, `pytest-asyncio>=0.23.0`. + +## How it works + +The demo runs entirely offline and walks three tool calls through the governance flow: + +1. **`init_assembly()`** is called with `gateway_url`, `api_key`, `agent_id="openai-agents-demo"`, and `mode="sdk-only"`, yielding an assembly context (`ctx`). The gateway URL defaults to `http://localhost:8080` and the API key is read from the environment (both optional offline). +2. A **`LocalPolicyEngine`** (from `src/policy.py`) acts as the policy interceptor, simulating the rules the gateway would enforce in production. It exposes `check_tool_start()` returning `"allow"`, `"deny"`, or `"pending"`, and `wait_for_tool_approval()` for the approval gate. +3. An **`AssemblyCallbackHandler`** is constructed with that engine as its `interceptor`. Each demo call invokes `handler.on_tool_start(...)` before the tool function runs. +4. The policy outcome drives the result: + - **allow** — the tool function runs and its return value is printed. + - **deny** — `delete_record` / `drop_table` raise `ToolExecutionBlockedError` via policy rule `deny_destructive_data_ops`. + - **pending** — `send_message_to_user` / `trigger_payment` require approval; offline there is no approver, so `wait_for_tool_approval()` denies them. + +For real OpenAI Agents SDK usage, Agent Assembly's `OpenAIAgentsPatch` intercepts `FunctionTool.__call__` at the framework level automatically once `init_assembly()` has run. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then, from the example directory: + +```bash +cd python/openai-agents-sdk +uv sync --extra dev +uv run python src/main.py +``` + +No `OPENAI_API_KEY` is required for the offline demo. + +## Code walkthrough + +The three tools are plain Python functions, classified by intent (`src/tools.py`): + +```python +def search_documents(query: str) -> str: + """Search the internal knowledge base for the given query.""" + return f"📄 Search results for '{query}': [doc-42, doc-17, doc-99] (mock)" + + +def delete_record(record_id: str) -> str: + """Permanently delete a database record.""" + return f"Record {record_id} deleted." +``` + +The local policy engine maps tool names to outcomes (`src/policy.py`): + +```python +DENIED_TOOLS: frozenset[str] = frozenset({ + "delete_record", + "drop_table", +}) + +APPROVAL_REQUIRED_TOOLS: frozenset[str] = frozenset({ + "send_message_to_user", + "trigger_payment", +}) +``` + +`main.py` wires the policy engine into the callback handler and runs each governed call: + +```python +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="openai-agents-demo", + mode="sdk-only", +) as ctx: + policy = LocalPolicyEngine() + handler = AssemblyCallbackHandler(interceptor=policy) +``` + +A blocked tool surfaces as `ToolExecutionBlockedError`, which the demo catches and prints: + +```python +try: + handler.on_tool_start( + serialized={"name": tool_name, "type": "tool"}, + input_str=input_str, + run_id=run_id, + ) + fn = _TOOL_FNS[tool_name] + result = fn(**json.loads(input_str)) + print(f" ✅ ALLOWED — {result}") +except ToolExecutionBlockedError as exc: + print(f" ❌ BLOCKED — {exc}") +``` + +## Notes & caveats + +!!! note "Offline mode" + The demo runs in `sdk-only` mode and requires no `OPENAI_API_KEY` and no running gateway. The `LocalPolicyEngine` simulates gateway policy locally. Tools requiring approval are auto-denied offline because no approver is available. + +!!! tip "Extending to a real agent" + With an `OPENAI_API_KEY` set, you can extend `main.py` to create a real `openai.agents.Agent` with your `FunctionTool` instances. Agent Assembly's `OpenAIAgentsPatch` intercepts every tool call automatically once `init_assembly()` has run. To configure production mode, copy `.env.example` to `.env` and set `AGENT_ASSEMBLY_GATEWAY_URL`, `AGENT_ASSEMBLY_API_KEY`, and `OPENAI_API_KEY`. + +!!! warning "Troubleshooting" + | Problem | Fix | + |---|---| + | `ModuleNotFoundError: agent_assembly` | Run `uv sync` first | + | `ModuleNotFoundError: openai` | Run `uv sync` — `openai-agents` is a required dependency | + | `ToolExecutionBlockedError` in tests | Expected — the deny/approval policy rules are intentional | + +## Expected behavior + +``` +============================================================== + Agent Assembly — OpenAI Agents SDK Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: openai-agents-demo + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — delete_record, drop_table (destructive data ops) + APPROVAL — send_message_to_user, trigger_payment + ALLOW — everything else + +Running governed tool calls: +-------------------------------------------- + → search_documents({"query": "agent governance best practices"}) + ✅ ALLOWED — 📄 Search results for 'agent governance best practices': ... + + → send_message_to_user({"user_id": "u-001", "message": "Your report is ready."}) + ❌ BLOCKED — Tool 'send_message_to_user' requires approval, but no approver is available in offline mode. + + → delete_record({"record_id": "rec-7829"}) + ❌ BLOCKED — Tool 'delete_record' is permanently blocked by policy rule 'deny_destructive_data_ops'. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/openai-agents-sdk) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/openai-agents-sdk/README.md) +- [OpenAI Agents SDK docs](https://openai.github.io/openai-agents-python/) From 0ef9a3ba4503d0f3bf21461b569b8b2fb98ecdc8 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20Pydantic=20AI=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/pydantic-ai.md | 196 +++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 docs/examples/pydantic-ai.md diff --git a/docs/examples/pydantic-ai.md b/docs/examples/pydantic-ai.md new file mode 100644 index 00000000..e20cd33f --- /dev/null +++ b/docs/examples/pydantic-ai.md @@ -0,0 +1,196 @@ +# Pydantic AI + +Integrate Agent Assembly with [Pydantic AI](https://ai.pydantic.dev/) to enforce governance policy on tool calls before they execute — driven fully offline. + +## What this example demonstrates + +- Initializing Agent Assembly with `init_assembly()` in offline `sdk-only` mode. +- Installing tool-level governance hooks with `PydanticAIAdapter`, which patches `pydantic_ai.tools.Tool._run`. +- Driving a real Pydantic AI `Agent` with the built-in `TestModel`, so the demo runs **offline with no API key**. +- Running an **allowed** tool call (`get_weather`), a **denied** tool call (`delete_records`), and a **pending** tool call (`send_email` — requires approval, auto-denied offline). +- How `PolicyViolationError` is raised when a tool is blocked or rejected during approval. + +## The framework / library + +This example builds on [Pydantic AI](https://ai.pydantic.dev/), the agent framework from the Pydantic team. + +The Agent Assembly Pydantic AI adapter hooks the internal `Tool._run` entry point, which exists in the Pydantic AI `0.1.x`–`0.2.x` line. Accordingly, `pyproject.toml` pins: + +```toml +dependencies = [ + "agent-assembly>=0.0.1a2", + # The Agent Assembly Pydantic AI adapter patches `Tool._run`, which is + # present in the 0.1.x–0.2.x line. Newer 1.x releases renamed that internal + # entry point; pin to the supported range so governance hooks attach. + "pydantic-ai>=0.1.0,<0.3.0", +] +``` + +Newer Pydantic AI `1.x` releases renamed that internal API, so the governance hooks would not attach there. The example requires Python `>= 3.12` and the Agent Assembly Python SDK `>= 0.0.1a2`. + +## How it works + +`init_assembly()` runs in `sdk-only` mode and, in production, auto-detects Pydantic AI and registers the adapter automatically. The `PydanticAIAdapter` patches `pydantic_ai.tools.Tool._run`, so every tool invocation is routed through a governance check before the tool body runs. + +In `src/main.py`, the adapter is registered *before* `init_assembly()`. Because the patch is idempotent, registering first makes `init_assembly`'s auto-detection a no-op and keeps the offline `LocalPolicyEngine` wired as the governance interceptor: + +```python +adapter = PydanticAIAdapter() +adapter.set_process_agent_id("pydantic-ai-demo-agent") +adapter.register_hooks(LocalPolicyEngine()) + +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="pydantic-ai-demo-agent", + mode="sdk-only", +) as ctx: + ... +``` + +In `src/agent.py`, the agent is built with `TestModel`, told which tool to call, so it deterministically exercises the allow / deny / pending paths offline. In `src/policy.py`, `LocalPolicyEngine.check_tool_start` returns a decision dict in the gateway wire format (`{"status": "allow" | "deny" | "pending"}`). A `deny`, or an unapproved `pending` resolved by `wait_for_tool_approval`, raises `PolicyViolationError` before the tool runs. + +The policy rules are: + +- **DENY** — `delete_records`, `write_file` (destructive operations) +- **PENDING** — `send_email` (requires human approval; auto-denied offline since no approver is available) +- **ALLOW** — everything else + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +This demo runs fully offline — no running Agent Assembly gateway and no LLM API key are required. + +```bash +cd python/pydantic-ai +uv sync --extra dev +uv run python src/main.py +``` + +## Code walkthrough + +The agent is wired with `TestModel` and told which tool to call, so each run deterministically triggers one tool (`src/agent.py`): + +```python +def build_agent(call_tool: str) -> Any: + from pydantic_ai import Agent + from pydantic_ai.models.test import TestModel + + agent = Agent(TestModel(call_tools=[call_tool])) + + @agent.tool_plain + def get_weather(city: str) -> str: + """Get the current weather for a city (safe — allowed by policy).""" + return f"Weather in {city}: 22C, partly cloudy (mock response)" + + @agent.tool_plain + def delete_records(path: str) -> str: + """Delete records at a path (destructive — denied by policy).""" + return f"Deleted records at {path}" + + @agent.tool_plain + def send_email(to: str) -> str: + """Send an email (requires approval — pending then denied offline).""" + return f"Email sent to {to}" + + return agent +``` + +The local policy engine simulates gateway enforcement, returning decisions in the gateway wire format (`src/policy.py`): + +```python +class LocalPolicyEngine: + async def check_tool_start(self, **kwargs: Any) -> dict[str, str]: + tool_name = str(kwargs.get("tool_name", "")) + if tool_name in DENIED_TOOLS: + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' is blocked by policy rule " + "'deny_destructive_operations'." + ), + } + if tool_name in PENDING_TOOLS: + return { + "status": "pending", + "reason": f"Tool '{tool_name}' requires human approval before execution.", + } + return {"status": "allow"} +``` + +Pending tools route to `wait_for_tool_approval`, which denies them offline because no approver is available (`src/policy.py`): + +```python + async def wait_for_tool_approval(self, **kwargs: Any) -> dict[str, str]: + """Offline mode: no approver is available, so pending tools are denied.""" + tool_name = str(kwargs.get("tool_name", "")) + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' requires approval, but no approver is available " + "in offline mode." + ), + } +``` + +## Notes & caveats + +!!! warning "Pydantic AI version pin" + The adapter hooks the internal `Tool._run` entry point, which exists in the Pydantic AI `0.1.x`–`0.2.x` line. `pyproject.toml` pins `pydantic-ai>=0.1.0,<0.3.0` so the governance hooks attach. Newer `1.x` releases renamed that internal API. If governance hooks do not fire, ensure `pydantic-ai` resolves to the pinned `0.1.x`–`0.2.x` range. + +!!! note "Offline TestModel" + The agent is driven by Pydantic AI's built-in `TestModel`, so the demo runs deterministically with no API key and no network. No running Agent Assembly gateway is required for the offline demo. + +!!! tip "Troubleshooting" + - `ModuleNotFoundError: agent_assembly` → run `uv sync` first. + - `ModuleNotFoundError: pydantic_ai` → run `uv sync`; `pydantic-ai` is a required dependency. + - Governance hooks do not fire → ensure `pydantic-ai` resolves to the pinned `0.1.x`–`0.2.x` range. + - `PolicyViolationError` in tests → expected; the deny/pending policy rules are intentional. + +To move to production mode: start an Agent Assembly gateway (or use your SaaS workspace URL), copy `.env.example` to `.env` and fill in credentials, swap `TestModel` for a real model (e.g. `openai:gpt-4o`) and set `OPENAI_API_KEY`, then run with the gateway environment variables: + +```bash +AGENT_ASSEMBLY_GATEWAY_URL=http://localhost:8080 \ +AGENT_ASSEMBLY_API_KEY=your-key \ +uv run python src/main.py +``` + +In production, `init_assembly()` auto-detects Pydantic AI and registers the adapter automatically, and the gateway enforces the policy rules — replacing `LocalPolicyEngine` with the gateway-backed interceptor. + +## Expected behavior + +``` +============================================================== + Agent Assembly — Pydantic AI Governed Agent Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: pydantic-ai-demo-agent + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — delete_records, write_file (destructive operations) + PENDING — send_email (requires human approval) + ALLOW — everything else + +Running governed tool calls (driven offline by TestModel): +-------------------------------------------- + → agent run that calls get_weather + ✅ ALLOWED — get_weather executed (mock response) + + → agent run that calls delete_records + ❌ BLOCKED — Tool 'delete_records' blocked by governance policy: Tool 'delete_records' is blocked by policy rule 'deny_destructive_operations'. + + → agent run that calls send_email + ❌ BLOCKED — Tool 'send_email' rejected during approval: Tool 'send_email' requires approval, but no approver is available in offline mode. + +Assembly context shut down. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/pydantic-ai) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/pydantic-ai/README.md) +- [Pydantic AI documentation](https://ai.pydantic.dev/) From 7a84b574860532b09e8ea9a49c863b87898e0b9a Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20Google=20ADK=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/google-adk.md | 166 ++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/examples/google-adk.md diff --git a/docs/examples/google-adk.md b/docs/examples/google-adk.md new file mode 100644 index 00000000..2676c1e4 --- /dev/null +++ b/docs/examples/google-adk.md @@ -0,0 +1,166 @@ +# Google ADK + +Integrates Agent Assembly with [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) to enforce governance policy on tool calls before they execute. + +## What this example demonstrates + +- Initializing Agent Assembly with `init_assembly()` in offline `sdk-only` mode. +- Installing Google ADK tool-level governance hooks — `GoogleADKAdapter` patches `google.adk.tools.BaseTool.run_async`. +- Running an **allowed** tool call (`get_weather`), a **denied** tool call (`delete_records`), and a **pending** tool call (`send_email`, which requires approval and is auto-denied offline). +- How `PolicyViolationError` is raised when a tool is blocked or rejected during approval. +- Replaying a **scripted tool trajectory** — no live Gemini / Vertex AI LLM and no cloud credentials are involved. + +## The framework / library + +[Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) is the agent framework governed in this example. + +Version pins (from `pyproject.toml`): + +| Dependency | Version | +|---|---| +| `google-adk` | `>=1.0.0,<2.0` | +| `agent-assembly` | `>=0.0.1a2` | +| Python | `>=3.12` | + +## How it works + +`init_assembly()` is opened as a context manager in offline `sdk-only` mode with the agent id `google-adk-demo-agent`: + +```python +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="google-adk-demo-agent", + mode="sdk-only", +) as ctx: + ... +``` + +**Offline note.** Google ADK normally drives its agent loop against a cloud LLM (Gemini / Vertex AI), which requires credentials and a live network call. To stay runnable with no secrets, the example does not start a live model. Instead it replays a *scripted tool trajectory*: it builds real ADK `BaseTool` instances (`src/tools.py`) and invokes `run_async` directly — the exact surface the governance adapter patches. The genuine allow / deny / pending governance code runs; only the LLM that would *choose* the tools is mocked out. + +**ADK 1.x adapter / version note.** In ADK 1.x, concrete tools (`FunctionTool` and custom `BaseTool` subclasses) override `run_async`, so patching the `BaseTool` base class alone does not intercept them. The example therefore applies the adapter's tool patch to the concrete demo tool class directly (see `src/governance.py`), the same mechanism the Agent Assembly SDK's own integration tests use. A future adapter release that patches concrete tool classes will let `init_assembly()`'s auto-detection wire this for you. + +The concrete `DemoTool` class is governed *before* `init_assembly()` so the offline `LocalPolicyEngine` stays wired as the interceptor (the patch is idempotent): + +```python +govern_tool_class(DemoTool, LocalPolicyEngine()) +``` + +The adapter calls `check_tool_start` (and, for pending tools, `wait_for_tool_approval`) as `async` hooks on `LocalPolicyEngine` (`src/policy.py`). These return decision dicts in the gateway wire format `{"status": "allow" | "deny" | "pending"}`. `delete_records` and `write_file` are denied; `send_email` is pending and, with no approver available offline, is denied during approval. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then run the example (offline — no Google Cloud credentials and no running gateway required): + +```bash +cd python/google-adk +uv sync --extra dev +uv run python src/main.py +``` + +## Code walkthrough + +Applying the adapter's tool patch to a concrete ADK tool class (`src/governance.py`): + +```python +from agent_assembly.adapters.google_adk import patch as google_adk_patch + + +def govern_tool_class(tool_cls: type[Any], interceptor: Any) -> None: + google_adk_patch._apply_tool_run_async_patch(tool_cls, interceptor) + + +def ungovern_tool_class(tool_cls: type[Any]) -> None: + google_adk_patch._revert_tool_run_async_patch(tool_cls) +``` + +The offline policy engine returns gateway-format decisions (`src/policy.py`): + +```python +async def check_tool_start(self, **kwargs: Any) -> dict[str, str]: + tool_name = str(kwargs.get("tool_name", "")) + if tool_name in DENIED_TOOLS: + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' is blocked by policy rule " + "'deny_destructive_operations'." + ), + } + if tool_name in PENDING_TOOLS: + return { + "status": "pending", + "reason": f"Tool '{tool_name}' requires human approval before execution.", + } + return {"status": "allow"} +``` + +For a pending tool, offline approval resolves to a deny because no approver exists (`src/policy.py`): + +```python +async def wait_for_tool_approval(self, **kwargs: Any) -> dict[str, str]: + """Offline mode: no approver is available, so pending tools are denied.""" + tool_name = str(kwargs.get("tool_name", "")) + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' requires approval, but no approver is available " + "in offline mode." + ), + } +``` + +## Notes & caveats + +!!! note "Scripted trajectory (offline)" + Google ADK drives its agent loop against a cloud LLM (Gemini / Vertex AI), which requires credentials and a network call. To keep the example runnable with no secrets, it does not start a live model — it replays a scripted tool trajectory that builds real ADK `BaseTool` instances and invokes `run_async` directly. The genuine allow / deny / pending governance code runs; only the LLM that would choose the tools is mocked out. + +!!! warning "ADK 1.x concrete-tool patch" + In ADK 1.x, concrete tools (`FunctionTool` and custom `BaseTool` subclasses) override `run_async`, so patching the `BaseTool` base class alone does not intercept them. This example applies the adapter's tool patch to the concrete demo tool class directly (see `src/governance.py`). With a future adapter release that patches concrete tool classes, `init_assembly()`'s auto-detection will wire this for you. + +Troubleshooting (from the README): + +| Problem | Fix | +|---|---| +| `ModuleNotFoundError: agent_assembly` | Run `uv sync` first | +| `ModuleNotFoundError: google.adk` | Run `uv sync` — `google-adk` is a required dependency | +| `PolicyViolationError` in tests | Expected — the deny/pending policy rules are intentional | + +## Expected behavior + +``` +============================================================== + Agent Assembly — Google ADK Governed Agent Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: google-adk-demo-agent + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — delete_records, write_file (destructive operations) + PENDING — send_email (requires human approval) + ALLOW — everything else + +Replaying scripted tool trajectory (no live LLM): +-------------------------------------------- + → get_weather(...) + ✅ ALLOWED — Weather: 22C, partly cloudy (mock response) + + → delete_records(...) + ❌ BLOCKED — Tool 'delete_records' blocked by governance policy: Tool 'delete_records' is blocked by policy rule 'deny_destructive_operations'. + + → send_email(...) + ❌ BLOCKED — Tool 'send_email' rejected during approval: Tool 'send_email' requires approval, but no approver is available in offline mode. + +Assembly context shut down. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/google-adk) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/google-adk/README.md) +- [Google ADK documentation](https://google.github.io/adk-docs/) From 88411747ecffbae075a0aabac1255c2c22cc68b7 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20LlamaIndex=20tool=20policy=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/llamaindex-tool-policy.md | 165 ++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/examples/llamaindex-tool-policy.md diff --git a/docs/examples/llamaindex-tool-policy.md b/docs/examples/llamaindex-tool-policy.md new file mode 100644 index 00000000..50805911 --- /dev/null +++ b/docs/examples/llamaindex-tool-policy.md @@ -0,0 +1,165 @@ +# LlamaIndex — manual tool policy + +Add Agent Assembly governance to [LlamaIndex](https://docs.llamaindex.ai/) tool calls by wrapping each `FunctionTool` with a `GovernedToolRunner`, since LlamaIndex has no native adapter yet. + +## What this example demonstrates + +Because LlamaIndex does not yet have a native Agent Assembly adapter, this example shows the **manual wrapper pattern**: each `FunctionTool` is wrapped with `GovernedToolRunner` so governance runs before every tool invocation. This pattern works for any Python callable. + +It walks through: + +- Initializing Agent Assembly with `init_assembly()`. +- Applying governance to `FunctionTool` calls using `GovernedToolRunner`. +- Running an **allowed** tool call (`query_index`). +- Running another **allowed** tool call (`summarize_docs`). +- Running a **denied** tool call (`execute_sql` — blocked by `deny_arbitrary_execution`). +- How to add governance to any framework that lacks a native adapter. + +## The framework / library + +[LlamaIndex](https://docs.llamaindex.ai/) — the example depends on `llama-index-core>=0.10.0` for its `FunctionTool` abstraction, and on the Agent Assembly Python SDK pinned at `agent-assembly>=0.0.1a2` (per `pyproject.toml`). Python `>=3.12` is required. + +## How it works + +`main.py` opens an Agent Assembly context with `init_assembly()`, passing `agent_id="llamaindex-demo-agent"` and `mode="sdk-only"` so the demo runs offline: + +```python +with init_assembly( + gateway_url=gateway_url, + api_key=api_key, + agent_id="llamaindex-demo-agent", + mode="sdk-only", +) as ctx: + ... +``` + +The `gateway_url` defaults to `http://localhost:8080` (read from `AGENT_ASSEMBLY_GATEWAY_URL`), and `api_key` comes from `AGENT_ASSEMBLY_API_KEY`. Neither is required for the offline demo. + +Each tool is then wrapped in a `GovernedToolRunner`, which holds the callable plus an `AssemblyCallbackHandler` configured with a `LocalPolicyEngine`. Calling `runner.run(**kwargs)` first fires `on_tool_start`, which routes through the policy engine before the underlying function executes. When the policy denies a tool (here, `execute_sql` and `run_shell_command`), the call surfaces as a `ToolExecutionBlockedError`, which `main.py` catches and prints as `❌ BLOCKED`. + +Unlike the native-adapter examples — where `init_assembly()` wires governance into the framework automatically — this is the fallback path for frameworks that lack a native adapter: you place the `GovernedToolRunner` in front of each callable yourself. As `main.py` notes, once a native LlamaIndex adapter exists, `GovernedToolRunner` will no longer be needed. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then: + +```bash +cd python/llamaindex-tool-policy +uv sync --extra dev +uv run python src/main.py +``` + +The demo runs fully offline — no API key and no running gateway are required. + +## Code walkthrough + +`policy.py` defines the local policy engine and the runner that bridges any callable into governance. The denied tools are a static set, and `check_tool_start` returns an allow/deny verdict: + +```python +DENIED_TOOLS: frozenset[str] = frozenset({ + "execute_sql", + "run_shell_command", +}) + + +class LocalPolicyEngine: + """Simulates Agent Assembly gateway policy enforcement in offline mode.""" + + def check_tool_start(self, serialized, input_str, run_id=None, **kwargs): + tool_name = serialized.get("name", "") + if tool_name in DENIED_TOOLS: + return { + "status": "deny", + "reason": ( + f"Tool '{tool_name}' is blocked by policy rule " + "'deny_arbitrary_execution'." + ), + } + return {"status": "allow"} +``` + +`GovernedToolRunner.run()` serializes the kwargs, calls the handler's `on_tool_start` to enforce policy, then invokes the wrapped function: + +```python +def run(self, **kwargs: Any) -> Any: + import json + + input_str = json.dumps(kwargs) + self._handler.on_tool_start( + serialized={"name": self._tool_name, "type": "tool"}, + input_str=input_str, + run_id=uuid4(), + ) + return self._fn(**kwargs) +``` + +`tools.py` defines the three LlamaIndex `FunctionTool`s used by the demo: + +```python +query_index = FunctionTool.from_defaults( + fn=_query_index_fn, + name="query_index", + description="Query the document index for relevant information.", +) +``` + +`main.py` builds a runner per tool and executes the demo calls: + +```python +runners = { + name: GovernedToolRunner(name, fn, policy) + for name, fn, _ in _DEMO_CALLS +} +``` + +## Notes & caveats + +!!! note "Manual pattern for frameworks without a native adapter" + LlamaIndex does not yet have a native Agent Assembly adapter, so governance is applied explicitly via `GovernedToolRunner`. When a native adapter becomes available, `init_assembly()` will apply governance automatically and the manual runner will no longer be needed. + +Troubleshooting (from the README): + +| Problem | Fix | +|---|---| +| `ModuleNotFoundError: agent_assembly` | Run `uv sync` first | +| `ModuleNotFoundError: llama_index` | Run `uv sync` — `llama-index-core` is a required dependency | +| `ToolExecutionBlockedError` in tests | Expected — the deny policy rule for `execute_sql` is intentional | + +## Expected behavior + +``` +============================================================== + Agent Assembly — LlamaIndex Tool Policy Demo +============================================================== + +Initializing Agent Assembly (gateway: http://localhost:8080, sdk-only mode)... + Agent: llamaindex-demo-agent + Gateway: http://localhost:8080 + Mode: sdk-only (offline demo) + +Policy rules (local simulation of gateway policy): + DENY — execute_sql, run_shell_command (arbitrary execution) + ALLOW — everything else + +Wrapping LlamaIndex tools with GovernedToolRunner... + Tools wrapped: query_index, summarize_docs, execute_sql + +Running governed tool calls: +-------------------------------------------- + → query_index({'query': 'what is Agent Assembly?'}) + ✅ ALLOWED — 📚 Index results for 'what is Agent Assembly?': ... + + → summarize_docs({'topic': 'policy enforcement'}) + ✅ ALLOWED — 📝 Summary for 'policy enforcement': Agent Assembly provides governance... + + → execute_sql({'sql': 'DROP TABLE users; --'}) + ❌ BLOCKED — Tool 'execute_sql' is blocked by policy rule 'deny_arbitrary_execution'. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/llamaindex-tool-policy) +- [Example README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/llamaindex-tool-policy/README.md) +- [LlamaIndex docs](https://docs.llamaindex.ai/) From 85d09b8d896a7e0ad9853c0512c8f2f2edc98abb Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:30:38 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20custom=20tool=20policy=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/custom-tool-policy.md | 149 ++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 docs/examples/custom-tool-policy.md diff --git a/docs/examples/custom-tool-policy.md b/docs/examples/custom-tool-policy.md new file mode 100644 index 00000000..462c31ee --- /dev/null +++ b/docs/examples/custom-tool-policy.md @@ -0,0 +1,149 @@ +# Custom tool policy (no framework) + +The simplest Agent Assembly integration — wrap plain Python functions with governance, no AI framework required. + +## What this example demonstrates + +This example shows how to add Agent Assembly governance to plain Python functions using the minimal `governed()` wrapper helper. It covers: + +- 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. +- The `governed()` pattern as the building block for the `GovernedToolRunner` shown in the [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) example. + +## The framework / library + +There is **no AI framework** in this example — it depends only on `agent-assembly`, plus pure Python. From `pyproject.toml`: + +```toml +dependencies = [ + "agent-assembly>=0.0.1a2", +] +``` + +No LangChain, LlamaIndex, or any agent framework is involved; the tools are ordinary Python callables. + +## How it works + +1. `init_assembly()` opens an Assembly context in `sdk-only` mode (offline demo — no gateway or API key needed). +2. `governed(tool_name, fn, policy)` wraps a plain callable, returning a new callable. +3. When the wrapped callable is invoked, the policy check runs **before** the function body via the `AssemblyCallbackHandler`'s `on_tool_start`. +4. If the policy denies the tool, `ToolExecutionBlockedError` surfaces and the original function (`fn`) is **never called**. + +In `main.py`, the four tools from `tools.py` are wrapped, then driven through a demo loop. Allowed calls return their result; denied calls raise `ToolExecutionBlockedError`, which the loop catches and reports as blocked. + +## Prerequisites & running it + +See [Preparing the runtime environment](preparing-the-runtime-environment.md) for the shared prerequisites. + +Then, from the example directory: + +```bash +cd python/custom-tool-policy +uv sync --extra dev +uv run python src/main.py +``` + +No API key, no gateway, and no AI framework are required. + +## Code walkthrough + +The `governed()` helper from `policy.py` — it wraps a callable so the policy check runs before the function body: + +```python +def governed(tool_name: str, fn: Any, policy: LocalPolicyEngine) -> Any: + handler = AssemblyCallbackHandler(interceptor=policy) + + def _wrapper(**kwargs: Any) -> Any: + import json + + handler.on_tool_start( + serialized={"name": tool_name, "type": "tool"}, + input_str=json.dumps(kwargs), + run_id=uuid4(), + ) + return fn(**kwargs) + + _wrapper.__name__ = tool_name + return _wrapper +``` + +A plain tool function from `tools.py` — no framework, just a regular callable: + +```python +def fetch_stock_price(ticker: str) -> str: + """Return the current stock price for a ticker symbol.""" + prices = {"AAPL": 211.30, "GOOG": 178.52, "MSFT": 430.00} + price = prices.get(ticker.upper(), 42.00) + return f"${price:.2f} (mock)" +``` + +The demo loop from `main.py` — allowed calls return a result, denied calls raise: + +```python +for tool_name, kwargs in _DEMO_CALLS: + print(f" → {tool_name}({kwargs})") + try: + result = tools[tool_name](**kwargs) + print(f" ✅ ALLOWED — {result}") + except ToolExecutionBlockedError as exc: + print(f" ❌ BLOCKED — {exc}") + print() +``` + +## Notes & caveats + +!!! note + `governed()` is the minimal building block. The wrapped function body **never runs** when the policy denies the tool — the `ToolExecutionBlockedError` is raised inside the wrapper before `fn(**kwargs)` is reached. + +!!! tip + This same `governed()` pattern is the foundation for the `GovernedToolRunner` shown in the [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) example. + +Troubleshooting (from the README): + +| 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 | + +The gateway URL defaults to `http://localhost:8080` and can be overridden via the `AGENT_ASSEMBLY_GATEWAY_URL` environment variable; `AGENT_ASSEMBLY_API_KEY` is read but not required in this offline demo. + +## Expected behavior + +``` +============================================================== + 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'. +``` + +## Links + +- [Example directory](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/custom-tool-policy) +- [README](https://github.com/ai-agent-assembly/agent-assembly-examples/blob/master/python/custom-tool-policy/README.md) +- [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) From c78b11f9cbdb0904989373c26805a9637139699c Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:31:16 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20>=20Preparing=20the=20runtime=20environment=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../preparing-the-runtime-environment.md | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/examples/preparing-the-runtime-environment.md diff --git a/docs/examples/preparing-the-runtime-environment.md b/docs/examples/preparing-the-runtime-environment.md new file mode 100644 index 00000000..38931b96 --- /dev/null +++ b/docs/examples/preparing-the-runtime-environment.md @@ -0,0 +1,140 @@ +# Preparing the runtime environment + +Every page in this section walks through a self-contained, cloneable example from the +[`agent-assembly-examples`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python) +repository. The examples share the same prerequisites and the same run shape, so this page +collects everything you need **once** — each per-framework page then only lists what is +specific to that example. + +!!! tip "Every example runs offline by default" + All nine examples are designed to run **with no API keys and no running gateway**. They + execute in `sdk-only` mode and simulate the gateway's policy enforcement locally, so you + can clone, install, and run them on a laptop with no credentials. Connecting to a real + gateway is an optional "production mode" step documented on each page. + +## Prerequisites + +| Requirement | Version | Notes | +|---|---|---| +| Python | `>= 3.12` | Every example targets Python 3.12+. | +| [uv](https://github.com/astral-sh/uv) | latest | The examples use `uv` for dependency management and running. `pip` also works. | +| Agent Assembly Python SDK | `>= 0.0.1a2` | Installed for you via each example's `pyproject.toml` (`agent-assembly>=0.0.1a2`). | +| Agent Assembly gateway | optional | **Not** required for the offline demos. Only needed for production mode. | + +## Install the SDK + +Each example declares `agent-assembly` as a dependency, so `uv sync` (below) pulls it in. +To install the SDK on its own in your own project: + +```bash +pip install agent-assembly +``` + +Some examples need a framework extra alongside the SDK (for instance LangChain pulls in +`langchain-core`, LangGraph pulls in `langgraph`, CrewAI offers an optional `live` extra). +The exact dependency pins live in each example's `pyproject.toml` and are installed by +`uv sync` — you do not need to install them by hand. + +## Clone the examples repository + +The examples live in a dedicated repository. Clone it and change into the `python/` +directory: + +```bash +git clone https://github.com/ai-agent-assembly/agent-assembly-examples.git +cd agent-assembly-examples/python +``` + +Each subdirectory under `python/` is a standalone project with its own `README.md`, +`pyproject.toml`, optional `.env.example`, and a `src/` entrypoint (`src/main.py`). + +## Running an example + +The flow is the same for every example — change into the example directory, install its +dependencies, then run its entrypoint: + +```bash +cd python/ +uv sync --extra dev +uv run python src/main.py +``` + +`uv sync --extra dev` installs the runtime dependencies plus the `dev` extras (the test +tooling). The entrypoint is always `src/main.py`. + +!!! note "Some examples take a `--mock` flag" + A few examples (`langchain-research-agent`, `crewai-research-crew`) accept a `--mock` + flag that replays a scripted, fully offline trajectory: + + ```bash + uv run python src/main.py --mock + ``` + + These examples also **auto-fall back to mock mode** whenever `OPENAI_API_KEY` is unset, + so `--mock` is mostly an explicit, CI-friendly way to force offline behavior. The + per-example pages call out which examples support it. The remaining examples are offline + by construction and need no flag. + +To run an example's tests: + +```bash +uv run pytest tests/ -v +``` + +## Mock mode vs. real-provider mode + +| Mode | Gateway | LLM provider | How to run | +|---|---|---|---| +| **Mock / offline** (default) | none — policy is simulated locally | none — tools return mock data or a `--mock` trajectory replays | `uv run python src/main.py` (add `--mock` where supported) | +| **Real provider / production** | a running gateway (or SaaS workspace) enforces policy server-side | a real LLM key (e.g. `OPENAI_API_KEY`) drives tool selection | set the gateway + provider env vars, drop `mode="sdk-only"`, swap the local policy engine for the gateway-backed interceptor | + +In **mock mode**, each example ships a `LocalPolicyEngine` (or example-specific variant such +as `BalancedPolicyEngine` / `CrewPolicyEngine`) that mirrors the gateway wire format locally. +This is what lets the demos run governance logic — allow / deny / pending — without contacting +a gateway. + +In **production mode**, you start a gateway and the SDK enforces the policy configured there +automatically. Each per-example page's *Prerequisites & running it* section shows the exact +production command. + +## Gateway and environment variables + +### What the examples read + +The examples read their gateway connection from these environment variables (resolved inside +each example's `src/main.py`): + +| Variable | Purpose | Default in the examples | +|---|---|---| +| `AGENT_ASSEMBLY_GATEWAY_URL` | Base URL of the governance gateway | `http://localhost:8080` | +| `AGENT_ASSEMBLY_API_KEY` | API key sent to the gateway | unset (offline) | +| `OPENAI_API_KEY` | Optional LLM provider key for real-provider mode | unset (mock) | + +Each example ships a `.env.example` you can copy to `.env` and fill in for production mode: + +```bash +cp .env.example .env +# edit .env, then run with the variables exported, e.g.: +AGENT_ASSEMBLY_GATEWAY_URL=http://localhost:8080 \ +AGENT_ASSEMBLY_API_KEY=your-key \ +uv run python src/main.py +``` + +!!! note "Example env vars vs. the SDK's own resolver" + The examples pass `gateway_url` / `api_key` **explicitly** to `init_assembly()`, reading + them from the `AGENT_ASSEMBLY_*` variables shown above and defaulting to + `http://localhost:8080`. The SDK *itself* has a separate built-in resolver chain that, + when you **omit** those arguments, reads `AASM_GATEWAY_URL` / `AASM_API_KEY`, then + `~/.aasm/config.yaml`, then probes a local gateway on `http://localhost:7391`. See + [Configuration](../configuration.md#resolution-order) for that resolver. The examples use + explicit arguments, so they use the `AGENT_ASSEMBLY_*` names and the `:8080` default — + follow each example's README for its exact commands. + +## Next steps + +- Start with [LangChain — basic agent](langchain-basic-agent.md) for the simplest end-to-end + governed agent. +- See the [Examples overview](index.md) for the full list and which governance control each + one demonstrates. +- For the universal `init_assembly()` pattern and adapter detection, see + [Framework support](framework-support.md). From 5ca37a88f04e35a593a78fec8b549c63bda2c07b Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:32:28 +0800 Subject: [PATCH 14/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(docs):=20Move=20fra?= =?UTF-8?q?mework-examples=20into=20Examples=20>=20Framework=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework-support.md} | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) rename docs/{guides/framework-examples.md => examples/framework-support.md} (65%) diff --git a/docs/guides/framework-examples.md b/docs/examples/framework-support.md similarity index 65% rename from docs/guides/framework-examples.md rename to docs/examples/framework-support.md index 7d90da7a..11e08318 100644 --- a/docs/guides/framework-examples.md +++ b/docs/examples/framework-support.md @@ -1,10 +1,15 @@ -# Framework examples +# Framework support Agent Assembly governs *third-party* agent frameworks without those frameworks needing to be aware of it. You call `init_assembly()` once; the SDK detects which supported frameworks are importable in your process and installs governance hooks for each, in priority order (see [Core Concepts](../concepts/index.md#the-adapter-pattern)). +This page is the adapter ↔ example status reference for the [Examples](index.md) section. For +the shared run instructions, see [Preparing the runtime environment](preparing-the-runtime-environment.md); +for a detailed, source-grounded walkthrough of each example, follow the per-framework pages +linked from the [Examples overview](index.md). + ## The universal pattern The integration is the same for every framework — there is no per-framework setup call: @@ -36,12 +41,13 @@ repository. | Framework | Adapter | Runnable example | | --- | --- | --- | -| LangChain | `agent_assembly.adapters.langchain` | ✅ Validated — see [Quick start](#langchain-quick-start) below (runs offline against a mock LLM), plus [`langchain-basic-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-basic-agent) and [`langchain-research-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-research-agent). | -| LangGraph | `agent_assembly.adapters.langgraph` | ✅ Validated — see [`langgraph`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langgraph) (state-graph governance; wraps `StateGraph.compile()`). | -| CrewAI | `agent_assembly.adapters.crewai` | ✅ Validated — see [`crewai-research-crew`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/crewai-research-crew) (multi-agent crew). | -| OpenAI Agents | `agent_assembly.adapters.openai_agents` | ✅ Validated — see [`openai-agents-sdk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/openai-agents-sdk). | -| Pydantic AI | `agent_assembly.adapters.pydantic_ai` | ✅ Validated — see [`pydantic-ai`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/pydantic-ai). | -| Google ADK | `agent_assembly.adapters.google_adk` | ✅ Validated — see [`google-adk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/google-adk). | +| LangChain | `agent_assembly.adapters.langchain` | ✅ Validated — see [Quick start](#langchain-quick-start) below (runs offline against a mock LLM), plus the [LangChain basic agent](langchain-basic-agent.md) and [LangChain research agent](langchain-research-agent.md) pages. | +| LangGraph | `agent_assembly.adapters.langgraph` | ✅ Validated — see [LangGraph node-level governance](langgraph.md) (state-graph governance; wraps the compiled `StateGraph`). | +| CrewAI | `agent_assembly.adapters.crewai` | ✅ Validated — see [CrewAI research crew](crewai-research-crew.md) (multi-agent crew). | +| OpenAI Agents | `agent_assembly.adapters.openai_agents` | ✅ Validated — see [OpenAI Agents SDK](openai-agents-sdk.md). | +| Pydantic AI | `agent_assembly.adapters.pydantic_ai` | ✅ Validated — see [Pydantic AI](pydantic-ai.md). | +| Google ADK | `agent_assembly.adapters.google_adk` | ✅ Validated — see [Google ADK](google-adk.md). | +| LlamaIndex | _no native adapter_ | ✅ Validated (manual wrapper) — see [LlamaIndex — manual tool policy](llamaindex-tool-policy.md); governs `FunctionTool` calls via `GovernedToolRunner`. | | MCP servers | `agent_assembly.adapters.mcp` | ⏳ Planned — adapter ships; a curated example is not yet vendored. | !!! note "Adapter present vs. example present" @@ -95,14 +101,16 @@ directory for additional in-repo runnable scripts and their status. Curated, end-to-end examples for each framework live in the central [`agent-assembly-examples`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python) -repository. Each directory is a self-contained, cloneable project: - -- [`langchain-basic-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-basic-agent) — a governed LangChain agent. -- [`langchain-research-agent`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langchain-research-agent) — a governed LangChain research agent. -- [`langgraph`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/langgraph) — LangGraph state-graph governance. -- [`crewai-research-crew`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/crewai-research-crew) — a CrewAI multi-agent crew. -- [`openai-agents-sdk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/openai-agents-sdk) — the OpenAI Agents SDK. -- [`pydantic-ai`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/pydantic-ai) — Pydantic AI. -- [`google-adk`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/google-adk) — Google ADK. -- [`llamaindex-tool-policy`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/llamaindex-tool-policy) — LlamaIndex tool policy. -- [`custom-tool-policy`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python/custom-tool-policy) — framework-free tool governance. +repository. Each directory is a self-contained, cloneable project. This section documents each +one in detail — start with [Preparing the runtime environment](preparing-the-runtime-environment.md), +then follow the per-framework page: + +- [LangChain — basic agent](langchain-basic-agent.md) — a governed LangChain agent. +- [LangChain — research agent](langchain-research-agent.md) — a governed LangChain ReAct research agent with a balanced policy. +- [LangGraph — node-level governance](langgraph.md) — LangGraph state-graph governance. +- [CrewAI — multi-agent research crew](crewai-research-crew.md) — a CrewAI multi-agent crew with delegation tracking. +- [OpenAI Agents SDK](openai-agents-sdk.md) — the OpenAI Agents SDK. +- [Pydantic AI](pydantic-ai.md) — Pydantic AI. +- [Google ADK](google-adk.md) — Google ADK. +- [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) — the manual wrapper pattern for a framework with no native adapter. +- [Custom tool policy (no framework)](custom-tool-policy.md) — framework-free tool governance. From e44cf3344c110539148bac2dc957daabffb372f3 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:32:49 +0800 Subject: [PATCH 15/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Add=20Examples?= =?UTF-8?q?=20section=20overview=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/examples/index.md | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/examples/index.md diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 00000000..3d97418e --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,45 @@ +# Examples + +End-to-end, runnable examples that govern real AI agent frameworks with Agent Assembly. Each +example is a self-contained project in the +[`agent-assembly-examples`](https://github.com/ai-agent-assembly/agent-assembly-examples/tree/master/python) +repository, and each page below walks through what it demonstrates, how the `init_assembly()` +adapter flow wires the framework, an annotated code walkthrough, and the expected output. + +!!! tip "Everything here runs offline" + Every example is designed to run with **no API keys and no running gateway** — it executes + in `sdk-only` mode and simulates the gateway's policy enforcement locally. Connecting to a + real gateway is an optional production-mode step documented on each page. + +## Start here + +1. [Preparing the runtime environment](preparing-the-runtime-environment.md) — the shared + prerequisites, install steps, and run commands that apply to **every** example. Read this + first. +2. [Framework support](framework-support.md) — the adapter ↔ example status reference and the + universal `init_assembly()` pattern. + +## The examples + +| Example | Framework | Governance focus | +|---|---|---| +| [LangChain — basic agent](langchain-basic-agent.md) | LangChain | Allow / deny / pending on tool calls via `AssemblyCallbackHandler`. | +| [LangChain — research agent](langchain-research-agent.md) | LangChain | A *balanced* policy: network allowlist, daily budget, tool-call logging, and credential-leak blocking. | +| [LangGraph — node-level governance](langgraph.md) | LangGraph | Node-level hooks on a compiled `StateGraph`; a denied tool halts the graph mid-execution. | +| [CrewAI — multi-agent research crew](crewai-research-crew.md) | CrewAI | Multi-agent delegation tracking, file-write approval, and a shared budget across agents. | +| [OpenAI Agents SDK](openai-agents-sdk.md) | OpenAI Agents SDK | Approval-gated and denied tool calls, intercepting `FunctionTool.__call__`. | +| [Pydantic AI](pydantic-ai.md) | Pydantic AI | Tool-call governance driven offline by the built-in `TestModel`. | +| [Google ADK](google-adk.md) | Google ADK | A scripted offline tool trajectory governing `BaseTool.run_async` — no cloud credentials. | +| [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) | LlamaIndex | The manual wrapper pattern (`GovernedToolRunner`) for a framework with no native adapter. | +| [Custom tool policy (no framework)](custom-tool-policy.md) | — | Govern plain Python functions with the minimal `governed()` helper — no AI framework required. | + +## How the examples fit together + +- **No native adapter?** [Custom tool policy](custom-tool-policy.md) shows the minimal + `governed()` building block; [LlamaIndex — manual tool policy](llamaindex-tool-policy.md) + builds on it with `GovernedToolRunner`. Use these patterns for any framework Agent Assembly + does not hook automatically. +- **Native adapter?** The LangChain, LangGraph, CrewAI, OpenAI Agents, Pydantic AI, and Google + ADK examples each rely on `init_assembly()` detecting the framework and installing its + governance hooks for you — see [Framework support](framework-support.md) for the full + adapter list and priority order. From 9769063d3c0424e93c734491406b81f93c5e26ec Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sun, 14 Jun 2026 20:34:00 +0800 Subject: [PATCH 16/16] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Wire=20Examples?= =?UTF-8?q?=20section=20into=20nav=20and=20inbound=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/configuration.md | 2 +- docs/guides/index.md | 6 +++++- docs/index.md | 2 +- docs/quick-start.md | 2 +- mkdocs.yml | 14 +++++++++++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b739cfb4..d6e0b01b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,6 +110,6 @@ For the conceptual difference between `mode` (*where* policy is enforced) and `e ## Next steps -- [Framework examples](guides/framework-examples.md) — wire the SDK into LangChain, CrewAI, and more. +- [Examples](examples/index.md) — wire the SDK into LangChain, CrewAI, and more. - [Handling allow/deny decisions](guides/handling-decisions.md) — catch and respond to policy denials. - [Troubleshooting](troubleshooting.md) — what each configuration error means. diff --git a/docs/guides/index.md b/docs/guides/index.md index c8bd6672..dea6b431 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -5,9 +5,13 @@ do the [Quick Start](../quick-start.md) first. | Guide | What it covers | | --- | --- | -| [Framework examples](framework-examples.md) | Wire the SDK into LangChain (validated, offline example) and the other supported frameworks; the universal `init_assembly()` pattern; adapter detection and priority. | | [Handling allow/deny decisions](handling-decisions.md) | Catch a policy denial, the exception hierarchy, MCP-specific blocks, and observe (dry-run) mode. | | [Type checking](type-checking.md) | Use the SDK's shipped types (PEP 561) with mypy / Pyright in your own project. | +For runnable, end-to-end framework integrations — LangChain, LangGraph, CrewAI, OpenAI Agents, +Pydantic AI, Google ADK, LlamaIndex, and a framework-free example — see the +[Examples](../examples/index.md) section. [Framework support](../examples/framework-support.md) +there covers the universal `init_assembly()` pattern, adapter detection, and priority. + For the *why* behind the design — the adapter pattern, modes, and lifecycle — see [Core Concepts](../concepts/index.md). diff --git a/docs/index.md b/docs/index.md index a14e7b1e..8d87fddd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ flowchart LR | --- | --- | | Install and govern your first agent in 5 minutes | **[Quick Start](quick-start.md)** | | Understand the adapter pattern, modes, and lifecycle | **[Core Concepts](concepts/index.md)** | -| See real framework integrations and decision handling | **[Guides](guides/framework-examples.md)** | +| See real framework integrations and decision handling | **[Examples](examples/index.md)** and **[Guides](guides/index.md)** | | Configure the gateway URL, API key, and modes | **[Configuration](configuration.md)** | | Look up a class, exception, or model | **[API Reference](api-reference/index.md)** | | Check Python / core-runtime compatibility and releases | **[Compatibility & Versioning](compatibility/index.md)** | diff --git a/docs/quick-start.md b/docs/quick-start.md index 47eab0e3..52503698 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -113,6 +113,6 @@ network/kernel interception — see [Core Concepts → Modes](concepts/index.md# - **[Core Concepts](concepts/index.md)** — the adapter pattern, the `init_assembly()` lifecycle, and the modes/enforcement model. -- **[Guides](guides/framework-examples.md)** — wire the SDK into the framework you actually use. +- **[Examples](examples/index.md)** — wire the SDK into the framework you actually use. - **[Configuration](configuration.md)** — drop the hard-coded URL and key; let the resolver chain find them. diff --git a/mkdocs.yml b/mkdocs.yml index 7956b88e..a1469f88 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -176,9 +176,21 @@ nav: - Architecture (deep dive): concepts/architecture.md - Guides: - guides/index.md - - Framework examples: guides/framework-examples.md - Handling allow/deny decisions: guides/handling-decisions.md - Type checking: guides/type-checking.md + - Examples: + - examples/index.md + - Preparing the runtime environment: examples/preparing-the-runtime-environment.md + - Framework support: examples/framework-support.md + - LangChain — basic agent: examples/langchain-basic-agent.md + - LangChain — research agent: examples/langchain-research-agent.md + - LangGraph — node-level governance: examples/langgraph.md + - CrewAI — multi-agent research crew: examples/crewai-research-crew.md + - OpenAI Agents SDK: examples/openai-agents-sdk.md + - Pydantic AI: examples/pydantic-ai.md + - Google ADK: examples/google-adk.md + - LlamaIndex — manual tool policy: examples/llamaindex-tool-policy.md + - Custom tool policy (no framework): examples/custom-tool-policy.md - Configuration: configuration.md - API Reference: - api-reference/index.md