hexgate 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hexgate-0.2.3/LICENSE +21 -0
- {hexgate-0.2.1/hexgate.egg-info → hexgate-0.2.3}/PKG-INFO +96 -48
- hexgate-0.2.1/PKG-INFO → hexgate-0.2.3/README.md +94 -85
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/google/runner.py +19 -3
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/langchain/agent.py +2 -2
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/langchain/wrapper.py +2 -2
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/openai/runner.py +11 -10
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/pydantic_ai/agent.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/pydantic_ai/wrapper.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/loader.py +4 -4
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/_common.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/hexgate.py +2 -2
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/main.py +2 -2
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/serve.py +3 -3
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cloud/__init__.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cloud/attenuate.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cloud/biscuit.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cloud/client.py +7 -7
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/rego.py +1 -1
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/wasm_engine.py +3 -3
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/streaming/__init__.py +1 -1
- hexgate-0.2.1/README.md → hexgate-0.2.3/hexgate.egg-info/PKG-INFO +132 -48
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate.egg-info/SOURCES.txt +1 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/pyproject.toml +4 -2
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/google/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/google/tools.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/google/wrapper.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/langchain/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/langchain/tools.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/openai/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/openai/tools.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/openai/wrapper.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/pydantic_ai/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/adapters/pydantic_ai/tools.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/builtin/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/builtin/researcher/agent.yaml +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/builtin/researcher/policy.yaml +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/builtin/researcher/system.md +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/factory.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/models.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/agents/prompts/agent_system.md +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/audit.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/bootstrap.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/chat.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/policy/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/policy/main.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/google.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/langchain.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/manifest.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/models.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/openai.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/pydantic_ai.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/register/register.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/cli/state.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/config/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/config/settings.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/command_policy.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/context.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/sandbox_runtime.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/srt.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/runtime/workspace.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/binding.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/bundle.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/constraints.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/decision.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/enforcer.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/errors.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/file_scope.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/models.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/policy.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/policy_set.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/rego_wasm.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/signing.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/security/source.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/streaming/events.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/streaming/normalize.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/bash.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/decorators.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/fetch.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/_common.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/edit_file.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/glob.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/grep.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/read_file.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/files/write_file.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/refund.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tools/websearch.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tracing/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/tracing/langfuse.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/utils/__init__.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate/utils/retry.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate.egg-info/dependency_links.txt +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate.egg-info/entry_points.txt +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate.egg-info/requires.txt +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/hexgate.egg-info/top_level.txt +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/setup.cfg +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/tests/test_bootstrap.py +0 -0
- {hexgate-0.2.1 → hexgate-0.2.3}/tests/test_demo.py +0 -0
hexgate-0.2.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hexamind
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexgate
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Hexgate — authorization infrastructure for AI agents (agent runtime + cloud client).
|
|
5
|
+
License-Expression: MIT
|
|
5
6
|
Requires-Python: >=3.13
|
|
6
7
|
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
7
9
|
Requires-Dist: bashlex>=0.18
|
|
8
10
|
Requires-Dist: biscuit-python>=0.4
|
|
9
11
|
Requires-Dist: cryptography>=42
|
|
@@ -33,21 +35,86 @@ Requires-Dist: jupyter; extra == "dev"
|
|
|
33
35
|
Requires-Dist: pytest>=8.4.1; extra == "dev"
|
|
34
36
|
Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"
|
|
35
37
|
Requires-Dist: ruff>=0.12.2; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
<div align="center">
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
<img src="./icon.svg" alt="Hexgate" width="96" height="96" />
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
- `gpt-5.4`
|
|
43
|
-
- `Linkup` web search
|
|
44
|
-
- Tavily-based page fetch
|
|
45
|
-
- `Langfuse` tracing
|
|
44
|
+
# Hexgate
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
**Authorization infrastructure for AI agents.**
|
|
47
|
+
Policy enforcement, signed policy bundles, per-request user scope, audit trail — for OpenAI Agents, LangChain, Google ADK, Pydantic AI, or a native runtime.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
[**Website**](https://hexgate.ai) · [Docs](https://docs.hexgate.ai) · [PyPI](https://pypi.org/project/hexgate/) · [Discussions](https://github.com/HexamindOrganisation/hexgate/discussions)
|
|
50
|
+
|
|
51
|
+
[](https://pypi.org/project/hexgate/)
|
|
52
|
+
[](https://github.com/HexamindOrganisation/hexgate/actions/workflows/tests.yml)
|
|
53
|
+
[](https://pypi.org/project/hexgate/)
|
|
54
|
+
[](LICENSE)
|
|
55
|
+
|
|
56
|
+
<br />
|
|
57
|
+
|
|
58
|
+
<img src="./assets/hero.png" alt="Control what your agents do — not just what they say. Policy decisions streaming live from the PolicyEnforcer." />
|
|
59
|
+
|
|
60
|
+
<br />
|
|
61
|
+
|
|
62
|
+
[Quick Start](#-quick-start--local-cli) · [Two paths](#-which-path-do-i-pick) · [Framework adapters](#-framework-agent-wrapping) · [Policy bundles](#-policy-bundles--compile-sign-enforce-wasm) · [User scope](#-user-scope--roles) · [Platform](#-hexgate-platform)
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## What is Hexgate?
|
|
69
|
+
|
|
70
|
+
Hexgate is two things that move together:
|
|
71
|
+
|
|
72
|
+
- **`hexgate` — the SDK.** A Python runtime that gates every tool call through a typed `Decision` (allow / deny / approval-required), wraps your existing OpenAI / LangChain / Google ADK / Pydantic AI agent without rewriting it, and threads per-request user identity through tracing + audit.
|
|
73
|
+
- **The Hexgate platform** *(optional)* — a FastAPI control plane + React dashboard for editing policy in a browser, minting per-project tokens, watching live decisions stream from a serving agent, and shipping signed WASM policy bundles to production.
|
|
74
|
+
|
|
75
|
+
You can use the SDK with nothing else (single-process REPL, YAML on disk). Or plug in the platform when you want auditable decisions in ClickHouse, a shared Playground UI, and live policy edits.
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
┌─────────────────────────────────────────┐
|
|
79
|
+
your code ───► │ create_agent / wrap_*_agent / Runner │
|
|
80
|
+
│ ↓ │
|
|
81
|
+
│ PolicyEnforcer.decide(role, tool) │
|
|
82
|
+
│ ↓ │
|
|
83
|
+
│ allow · deny · approval_required │
|
|
84
|
+
└────────────────────┬────────────────────┘
|
|
85
|
+
│
|
|
86
|
+
┌────────────────────────┼─────────────────────────┐
|
|
87
|
+
▼ ▼ ▼
|
|
88
|
+
┌────────────────┐ ┌──────────────────┐ ┌────────────────┐
|
|
89
|
+
│ Local policy │ │ Signed WASM │ │ Audit log │
|
|
90
|
+
│ (YAML / dir, │ │ bundle from │ │ (ClickHouse │
|
|
91
|
+
│ hot reload) │ │ Hexgate cloud │ │ via REST) │
|
|
92
|
+
└────────────────┘ └──────────────────┘ └────────────────┘
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Table of contents
|
|
96
|
+
|
|
97
|
+
- [Prerequisites](#-prerequisites)
|
|
98
|
+
- [Quick Start — Local CLI](#-quick-start--local-cli)
|
|
99
|
+
- [Which path do I pick?](#-which-path-do-i-pick) — chat vs serve
|
|
100
|
+
- [Quick Start — Platform](#-quick-start--platform)
|
|
101
|
+
- [Core primitives](#-core-primitives)
|
|
102
|
+
- [Build an agent — end to end](#-build-an-agent--end-to-end)
|
|
103
|
+
- [What you can import](#-what-you-can-import)
|
|
104
|
+
- [Framework agent wrapping](#-framework-agent-wrapping) — OpenAI, LangChain, Google ADK, Pydantic AI
|
|
105
|
+
- [Define agents in code](#-define-agents-in-code)
|
|
106
|
+
- [Builtin and local agents](#-builtin-and-local-agents)
|
|
107
|
+
- [Policy shape](#-policy-shape)
|
|
108
|
+
- [Tool-call policy enforcement](#-tool-call-policy-enforcement)
|
|
109
|
+
- [Policy bundles — compile, sign, enforce (WASM)](#-policy-bundles--compile-sign-enforce-wasm)
|
|
110
|
+
- [Approval-required tool calls](#-approval-required-tool-calls)
|
|
111
|
+
- [Workspace sandbox](#-workspace-sandbox)
|
|
112
|
+
- [Environment](#-environment)
|
|
113
|
+
- [Tests & dev tooling](#-tests--dev-tooling)
|
|
114
|
+
- [CLI reference](#-cli-reference)
|
|
115
|
+
- [Hexgate platform](#-hexgate-platform)
|
|
116
|
+
- [User scope + roles](#-user-scope--roles)
|
|
117
|
+
- [Stream results](#-stream-results)
|
|
51
118
|
|
|
52
119
|
## 🛠️ Prerequisites
|
|
53
120
|
|
|
@@ -119,7 +186,7 @@ Both commands accept either a plain agent id (`--agent researcher`) or a uvicorn
|
|
|
119
186
|
|
|
120
187
|
## 🚀 Quick Start — Platform
|
|
121
188
|
|
|
122
|
-
To run the full
|
|
189
|
+
To run the full Hexgate control plane locally (FastAPI backend + dashboard + your local agent serving over WebSocket), you need **three terminals**. The Makefile has a target that prints the recipe:
|
|
123
190
|
|
|
124
191
|
```bash
|
|
125
192
|
make demo-platform # prints the 3-terminal recipe below
|
|
@@ -602,7 +669,7 @@ What happens under the hood:
|
|
|
602
669
|
|
|
603
670
|
Working scripts in `examples/`:
|
|
604
671
|
|
|
605
|
-
- `examples/customer_bot.py` — canonical
|
|
672
|
+
- `examples/customer_bot.py` — canonical Hexgate path: `create_agent(...)` + the dashboard register/serve loop end-to-end.
|
|
606
673
|
- `examples/openai_demo.py` — `HexgateRunner` (OpenAI Agents SDK) end-to-end.
|
|
607
674
|
- `examples/google_demo.py` — `HexgateRunner` (Google ADK) end-to-end with `InMemorySessionService`.
|
|
608
675
|
- `examples/pydantic_ai_demo.py` — `wrap_pydantic_agent` (Pydantic AI) end-to-end.
|
|
@@ -737,7 +804,7 @@ That means the same agent code can stay simple in development, while deployment
|
|
|
737
804
|
|
|
738
805
|
## 🧩 Policy Bundles — Compile, Sign, Enforce (WASM)
|
|
739
806
|
|
|
740
|
-
|
|
807
|
+
Hexgate has **two policy enforcement engines** that return identical decisions (there's a parity test suite that proves it):
|
|
741
808
|
|
|
742
809
|
- **pydantic** (default) — evaluates constraints in-process. Zero setup; this is what every example above uses.
|
|
743
810
|
- **WASM** — compiles `policy.yaml` → Rego → a WebAssembly module evaluated via `wasmtime`. This is the path production ships: one compiled artifact, byte-for-byte reproducible, cryptographically signed by the platform.
|
|
@@ -1059,44 +1126,21 @@ The platform-side test suite is separate and lives at `platform/api/tests/`:
|
|
|
1059
1126
|
cd platform/api && uv run pytest tests/
|
|
1060
1127
|
```
|
|
1061
1128
|
|
|
1062
|
-
##
|
|
1063
|
-
|
|
1064
|
-
Install the package into your current environment:
|
|
1065
|
-
|
|
1066
|
-
```bash
|
|
1067
|
-
python -m pip install -e .
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
Run the config-driven demo:
|
|
1071
|
-
|
|
1072
|
-
```bash
|
|
1073
|
-
python examples/demo.py
|
|
1074
|
-
```
|
|
1075
|
-
|
|
1076
|
-
Run the inline chat CLI with a local or builtin YAML agent:
|
|
1077
|
-
|
|
1078
|
-
```bash
|
|
1079
|
-
hexgate chat --agent example_agent
|
|
1080
|
-
```
|
|
1129
|
+
## 🖥️ CLI reference
|
|
1081
1130
|
|
|
1082
|
-
|
|
1131
|
+
The `hexgate` binary exposes `chat`, `serve`, `register`, and `policy` subcommands. The [Quick Start](#-quick-start--local-cli) covers `chat`; this section drills into `register` and `serve` for the platform path.
|
|
1083
1132
|
|
|
1084
1133
|
```bash
|
|
1134
|
+
hexgate --help # list subcommands
|
|
1135
|
+
hexgate <subcommand> --help # flags for a subcommand
|
|
1136
|
+
hexgate chat --list-agents # show resolvable agents
|
|
1085
1137
|
hexgate chat --use examples/file_agents.py --agent workspace_explorer
|
|
1086
|
-
hexgate chat --use examples/file_agents.py --agent repo_editor
|
|
1087
|
-
hexgate chat --use examples/research_agents.py --agent update_researcher
|
|
1088
1138
|
hexgate chat --use examples/research_agents.py --agent update_researcher --approval-mode ask
|
|
1089
1139
|
```
|
|
1090
1140
|
|
|
1091
|
-
List what the CLI can currently resolve:
|
|
1092
|
-
|
|
1093
|
-
```bash
|
|
1094
|
-
hexgate chat --list-agents
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
1141
|
### `hexgate register` — push a manifest to the platform
|
|
1098
1142
|
|
|
1099
|
-
Register a code-defined agent's manifest with the
|
|
1143
|
+
Register a code-defined agent's manifest with the Hexgate platform. `--agent`
|
|
1100
1144
|
takes a Python import path of the form `module.path:attribute`, the same shape
|
|
1101
1145
|
as ASGI/WSGI entrypoints. The CLI imports the module, grabs the agent object,
|
|
1102
1146
|
and POSTs its manifest to `${HEXGATE_API_URL}/v1/agents` using
|
|
@@ -1135,7 +1179,7 @@ system prompt directly off the object. No flags needed.
|
|
|
1135
1179
|
`--system-prompt` accepts either a literal string or a path to a `.md` /
|
|
1136
1180
|
`.txt` / `.jinja` file (read as text at register time).
|
|
1137
1181
|
|
|
1138
|
-
Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph,
|
|
1182
|
+
Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph, Hexgate agents.
|
|
1139
1183
|
|
|
1140
1184
|
### `hexgate serve` — bridge a local agent to the platform's relay
|
|
1141
1185
|
|
|
@@ -1172,7 +1216,7 @@ print(manifest.model_dump())
|
|
|
1172
1216
|
```
|
|
1173
1217
|
|
|
1174
1218
|
`create_manifest` dispatches on the framework of `agent`. The supported
|
|
1175
|
-
types are the same set `hexgate register` accepts:
|
|
1219
|
+
types are the same set `hexgate register` accepts: Hexgate, OpenAI Agents
|
|
1176
1220
|
SDK, Google ADK, Pydantic AI, and LangChain/LangGraph compiled graphs.
|
|
1177
1221
|
For LangGraph you must pass `tools=` explicitly, and may pass `model=` /
|
|
1178
1222
|
`system_prompt=`, since compiled graphs don't expose those fields after
|
|
@@ -1182,7 +1226,7 @@ The return value is an `AgentManifest` (a Pydantic model, also re-exported
|
|
|
1182
1226
|
from `hexgate` for type annotations) — the same schema the platform
|
|
1183
1227
|
stores and the dashboard renders.
|
|
1184
1228
|
|
|
1185
|
-
## 🌐
|
|
1229
|
+
## 🌐 Hexgate Platform
|
|
1186
1230
|
|
|
1187
1231
|
The `platform/` directory contains an optional control plane that hosts agent definitions, dev tokens, and a live debug surface. The SDK works fully without it (`load_local_agent`, `load_builtin_agent` keep their existing semantics) — but with it you get:
|
|
1188
1232
|
|
|
@@ -1301,7 +1345,7 @@ the name from the loaded agent's `.name` attribute — no env var needed.
|
|
|
1301
1345
|
|
|
1302
1346
|
## 👤 User Scope + Roles
|
|
1303
1347
|
|
|
1304
|
-
Real backends serve many users, and different users get different capabilities.
|
|
1348
|
+
Real backends serve many users, and different users get different capabilities. Hexgate splits that into two pieces:
|
|
1305
1349
|
|
|
1306
1350
|
- **`User`** — the per-request scope. Marks "this invocation acts on behalf of alice, in role X." Async context manager; pushes a fact-bearing Biscuit through the agent runtime.
|
|
1307
1351
|
- **Role policies** — one `policy.yaml` per role, optionally inheriting from a base mixin. The runtime picks the right one at call time based on the active `User.role`.
|
|
@@ -1451,3 +1495,7 @@ async for event in stream_agent(agent, handler, "latest AI breakthroughs"):
|
|
|
1451
1495
|
- assistant text deltas
|
|
1452
1496
|
- tool lifecycle
|
|
1453
1497
|
- final run completion
|
|
1498
|
+
|
|
1499
|
+
---
|
|
1500
|
+
|
|
1501
|
+
If Hexgate looks useful, [give it a ⭐ on GitHub](https://github.com/HexamindOrganisation/hexgate) — it helps more than you'd think. Built by [Hexamind](https://hexgate.ai).
|
|
@@ -1,53 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
Name: hexgate
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: HexaGate — authorization infrastructure for AI agents (agent runtime + cloud client).
|
|
5
|
-
Requires-Python: >=3.13
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: bashlex>=0.18
|
|
8
|
-
Requires-Dist: biscuit-python>=0.4
|
|
9
|
-
Requires-Dist: cryptography>=42
|
|
10
|
-
Requires-Dist: httpx>=0.28.1
|
|
11
|
-
Requires-Dist: langchain
|
|
12
|
-
Requires-Dist: langchain-openai
|
|
13
|
-
Requires-Dist: langchain-core
|
|
14
|
-
Requires-Dist: langfuse
|
|
15
|
-
Requires-Dist: pydantic>=2.12.4
|
|
16
|
-
Requires-Dist: python-dotenv>=1.1.1
|
|
17
|
-
Requires-Dist: pyyaml>=6.0.2
|
|
18
|
-
Requires-Dist: rich>=13.9.4
|
|
19
|
-
Requires-Dist: websockets>=13.0
|
|
20
|
-
Requires-Dist: openai-agents>=0.0.10
|
|
21
|
-
Requires-Dist: langgraph>=0.2
|
|
22
|
-
Requires-Dist: nest_asyncio>=1.6
|
|
23
|
-
Requires-Dist: openinference-instrumentation-openai-agents>=0.1
|
|
24
|
-
Requires-Dist: google-adk>=1.0
|
|
25
|
-
Requires-Dist: google-genai>=1.0
|
|
26
|
-
Requires-Dist: litellm>=1.50
|
|
27
|
-
Requires-Dist: openinference-instrumentation-google-adk>=0.1.11
|
|
28
|
-
Requires-Dist: pydantic-ai-slim>=1.88.0
|
|
29
|
-
Requires-Dist: wasmtime>=20.0
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: ipykernel; extra == "dev"
|
|
32
|
-
Requires-Dist: jupyter; extra == "dev"
|
|
33
|
-
Requires-Dist: pytest>=8.4.1; extra == "dev"
|
|
34
|
-
Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"
|
|
35
|
-
Requires-Dist: ruff>=0.12.2; extra == "dev"
|
|
36
|
-
|
|
37
|
-
# hexgate
|
|
38
|
-
|
|
39
|
-
`hexgate` is a lightweight LangChain-based agent runtime built around:
|
|
40
|
-
|
|
41
|
-
- `langchain`
|
|
42
|
-
- `gpt-5.4`
|
|
43
|
-
- `Linkup` web search
|
|
44
|
-
- Tavily-based page fetch
|
|
45
|
-
- `Langfuse` tracing
|
|
46
|
-
|
|
47
|
-
This package is intentionally small. The first milestone is a single assistant with:
|
|
1
|
+
<div align="center">
|
|
48
2
|
|
|
49
|
-
|
|
50
|
-
|
|
3
|
+
<img src="./icon.svg" alt="Hexgate" width="96" height="96" />
|
|
4
|
+
|
|
5
|
+
# Hexgate
|
|
6
|
+
|
|
7
|
+
**Authorization infrastructure for AI agents.**
|
|
8
|
+
Policy enforcement, signed policy bundles, per-request user scope, audit trail — for OpenAI Agents, LangChain, Google ADK, Pydantic AI, or a native runtime.
|
|
9
|
+
|
|
10
|
+
[**Website**](https://hexgate.ai) · [Docs](https://docs.hexgate.ai) · [PyPI](https://pypi.org/project/hexgate/) · [Discussions](https://github.com/HexamindOrganisation/hexgate/discussions)
|
|
11
|
+
|
|
12
|
+
[](https://pypi.org/project/hexgate/)
|
|
13
|
+
[](https://github.com/HexamindOrganisation/hexgate/actions/workflows/tests.yml)
|
|
14
|
+
[](https://pypi.org/project/hexgate/)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
|
|
17
|
+
<br />
|
|
18
|
+
|
|
19
|
+
<img src="./assets/hero.png" alt="Control what your agents do — not just what they say. Policy decisions streaming live from the PolicyEnforcer." />
|
|
20
|
+
|
|
21
|
+
<br />
|
|
22
|
+
|
|
23
|
+
[Quick Start](#-quick-start--local-cli) · [Two paths](#-which-path-do-i-pick) · [Framework adapters](#-framework-agent-wrapping) · [Policy bundles](#-policy-bundles--compile-sign-enforce-wasm) · [User scope](#-user-scope--roles) · [Platform](#-hexgate-platform)
|
|
24
|
+
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What is Hexgate?
|
|
30
|
+
|
|
31
|
+
Hexgate is two things that move together:
|
|
32
|
+
|
|
33
|
+
- **`hexgate` — the SDK.** A Python runtime that gates every tool call through a typed `Decision` (allow / deny / approval-required), wraps your existing OpenAI / LangChain / Google ADK / Pydantic AI agent without rewriting it, and threads per-request user identity through tracing + audit.
|
|
34
|
+
- **The Hexgate platform** *(optional)* — a FastAPI control plane + React dashboard for editing policy in a browser, minting per-project tokens, watching live decisions stream from a serving agent, and shipping signed WASM policy bundles to production.
|
|
35
|
+
|
|
36
|
+
You can use the SDK with nothing else (single-process REPL, YAML on disk). Or plug in the platform when you want auditable decisions in ClickHouse, a shared Playground UI, and live policy edits.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
┌─────────────────────────────────────────┐
|
|
40
|
+
your code ───► │ create_agent / wrap_*_agent / Runner │
|
|
41
|
+
│ ↓ │
|
|
42
|
+
│ PolicyEnforcer.decide(role, tool) │
|
|
43
|
+
│ ↓ │
|
|
44
|
+
│ allow · deny · approval_required │
|
|
45
|
+
└────────────────────┬────────────────────┘
|
|
46
|
+
│
|
|
47
|
+
┌────────────────────────┼─────────────────────────┐
|
|
48
|
+
▼ ▼ ▼
|
|
49
|
+
┌────────────────┐ ┌──────────────────┐ ┌────────────────┐
|
|
50
|
+
│ Local policy │ │ Signed WASM │ │ Audit log │
|
|
51
|
+
│ (YAML / dir, │ │ bundle from │ │ (ClickHouse │
|
|
52
|
+
│ hot reload) │ │ Hexgate cloud │ │ via REST) │
|
|
53
|
+
└────────────────┘ └──────────────────┘ └────────────────┘
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Table of contents
|
|
57
|
+
|
|
58
|
+
- [Prerequisites](#-prerequisites)
|
|
59
|
+
- [Quick Start — Local CLI](#-quick-start--local-cli)
|
|
60
|
+
- [Which path do I pick?](#-which-path-do-i-pick) — chat vs serve
|
|
61
|
+
- [Quick Start — Platform](#-quick-start--platform)
|
|
62
|
+
- [Core primitives](#-core-primitives)
|
|
63
|
+
- [Build an agent — end to end](#-build-an-agent--end-to-end)
|
|
64
|
+
- [What you can import](#-what-you-can-import)
|
|
65
|
+
- [Framework agent wrapping](#-framework-agent-wrapping) — OpenAI, LangChain, Google ADK, Pydantic AI
|
|
66
|
+
- [Define agents in code](#-define-agents-in-code)
|
|
67
|
+
- [Builtin and local agents](#-builtin-and-local-agents)
|
|
68
|
+
- [Policy shape](#-policy-shape)
|
|
69
|
+
- [Tool-call policy enforcement](#-tool-call-policy-enforcement)
|
|
70
|
+
- [Policy bundles — compile, sign, enforce (WASM)](#-policy-bundles--compile-sign-enforce-wasm)
|
|
71
|
+
- [Approval-required tool calls](#-approval-required-tool-calls)
|
|
72
|
+
- [Workspace sandbox](#-workspace-sandbox)
|
|
73
|
+
- [Environment](#-environment)
|
|
74
|
+
- [Tests & dev tooling](#-tests--dev-tooling)
|
|
75
|
+
- [CLI reference](#-cli-reference)
|
|
76
|
+
- [Hexgate platform](#-hexgate-platform)
|
|
77
|
+
- [User scope + roles](#-user-scope--roles)
|
|
78
|
+
- [Stream results](#-stream-results)
|
|
51
79
|
|
|
52
80
|
## 🛠️ Prerequisites
|
|
53
81
|
|
|
@@ -119,7 +147,7 @@ Both commands accept either a plain agent id (`--agent researcher`) or a uvicorn
|
|
|
119
147
|
|
|
120
148
|
## 🚀 Quick Start — Platform
|
|
121
149
|
|
|
122
|
-
To run the full
|
|
150
|
+
To run the full Hexgate control plane locally (FastAPI backend + dashboard + your local agent serving over WebSocket), you need **three terminals**. The Makefile has a target that prints the recipe:
|
|
123
151
|
|
|
124
152
|
```bash
|
|
125
153
|
make demo-platform # prints the 3-terminal recipe below
|
|
@@ -602,7 +630,7 @@ What happens under the hood:
|
|
|
602
630
|
|
|
603
631
|
Working scripts in `examples/`:
|
|
604
632
|
|
|
605
|
-
- `examples/customer_bot.py` — canonical
|
|
633
|
+
- `examples/customer_bot.py` — canonical Hexgate path: `create_agent(...)` + the dashboard register/serve loop end-to-end.
|
|
606
634
|
- `examples/openai_demo.py` — `HexgateRunner` (OpenAI Agents SDK) end-to-end.
|
|
607
635
|
- `examples/google_demo.py` — `HexgateRunner` (Google ADK) end-to-end with `InMemorySessionService`.
|
|
608
636
|
- `examples/pydantic_ai_demo.py` — `wrap_pydantic_agent` (Pydantic AI) end-to-end.
|
|
@@ -737,7 +765,7 @@ That means the same agent code can stay simple in development, while deployment
|
|
|
737
765
|
|
|
738
766
|
## 🧩 Policy Bundles — Compile, Sign, Enforce (WASM)
|
|
739
767
|
|
|
740
|
-
|
|
768
|
+
Hexgate has **two policy enforcement engines** that return identical decisions (there's a parity test suite that proves it):
|
|
741
769
|
|
|
742
770
|
- **pydantic** (default) — evaluates constraints in-process. Zero setup; this is what every example above uses.
|
|
743
771
|
- **WASM** — compiles `policy.yaml` → Rego → a WebAssembly module evaluated via `wasmtime`. This is the path production ships: one compiled artifact, byte-for-byte reproducible, cryptographically signed by the platform.
|
|
@@ -1059,44 +1087,21 @@ The platform-side test suite is separate and lives at `platform/api/tests/`:
|
|
|
1059
1087
|
cd platform/api && uv run pytest tests/
|
|
1060
1088
|
```
|
|
1061
1089
|
|
|
1062
|
-
##
|
|
1090
|
+
## 🖥️ CLI reference
|
|
1063
1091
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
```bash
|
|
1067
|
-
python -m pip install -e .
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
Run the config-driven demo:
|
|
1071
|
-
|
|
1072
|
-
```bash
|
|
1073
|
-
python examples/demo.py
|
|
1074
|
-
```
|
|
1075
|
-
|
|
1076
|
-
Run the inline chat CLI with a local or builtin YAML agent:
|
|
1077
|
-
|
|
1078
|
-
```bash
|
|
1079
|
-
hexgate chat --agent example_agent
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
Run the CLI with code-defined agents from a Python script:
|
|
1092
|
+
The `hexgate` binary exposes `chat`, `serve`, `register`, and `policy` subcommands. The [Quick Start](#-quick-start--local-cli) covers `chat`; this section drills into `register` and `serve` for the platform path.
|
|
1083
1093
|
|
|
1084
1094
|
```bash
|
|
1095
|
+
hexgate --help # list subcommands
|
|
1096
|
+
hexgate <subcommand> --help # flags for a subcommand
|
|
1097
|
+
hexgate chat --list-agents # show resolvable agents
|
|
1085
1098
|
hexgate chat --use examples/file_agents.py --agent workspace_explorer
|
|
1086
|
-
hexgate chat --use examples/file_agents.py --agent repo_editor
|
|
1087
|
-
hexgate chat --use examples/research_agents.py --agent update_researcher
|
|
1088
1099
|
hexgate chat --use examples/research_agents.py --agent update_researcher --approval-mode ask
|
|
1089
1100
|
```
|
|
1090
1101
|
|
|
1091
|
-
List what the CLI can currently resolve:
|
|
1092
|
-
|
|
1093
|
-
```bash
|
|
1094
|
-
hexgate chat --list-agents
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
1102
|
### `hexgate register` — push a manifest to the platform
|
|
1098
1103
|
|
|
1099
|
-
Register a code-defined agent's manifest with the
|
|
1104
|
+
Register a code-defined agent's manifest with the Hexgate platform. `--agent`
|
|
1100
1105
|
takes a Python import path of the form `module.path:attribute`, the same shape
|
|
1101
1106
|
as ASGI/WSGI entrypoints. The CLI imports the module, grabs the agent object,
|
|
1102
1107
|
and POSTs its manifest to `${HEXGATE_API_URL}/v1/agents` using
|
|
@@ -1135,7 +1140,7 @@ system prompt directly off the object. No flags needed.
|
|
|
1135
1140
|
`--system-prompt` accepts either a literal string or a path to a `.md` /
|
|
1136
1141
|
`.txt` / `.jinja` file (read as text at register time).
|
|
1137
1142
|
|
|
1138
|
-
Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph,
|
|
1143
|
+
Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph, Hexgate agents.
|
|
1139
1144
|
|
|
1140
1145
|
### `hexgate serve` — bridge a local agent to the platform's relay
|
|
1141
1146
|
|
|
@@ -1172,7 +1177,7 @@ print(manifest.model_dump())
|
|
|
1172
1177
|
```
|
|
1173
1178
|
|
|
1174
1179
|
`create_manifest` dispatches on the framework of `agent`. The supported
|
|
1175
|
-
types are the same set `hexgate register` accepts:
|
|
1180
|
+
types are the same set `hexgate register` accepts: Hexgate, OpenAI Agents
|
|
1176
1181
|
SDK, Google ADK, Pydantic AI, and LangChain/LangGraph compiled graphs.
|
|
1177
1182
|
For LangGraph you must pass `tools=` explicitly, and may pass `model=` /
|
|
1178
1183
|
`system_prompt=`, since compiled graphs don't expose those fields after
|
|
@@ -1182,7 +1187,7 @@ The return value is an `AgentManifest` (a Pydantic model, also re-exported
|
|
|
1182
1187
|
from `hexgate` for type annotations) — the same schema the platform
|
|
1183
1188
|
stores and the dashboard renders.
|
|
1184
1189
|
|
|
1185
|
-
## 🌐
|
|
1190
|
+
## 🌐 Hexgate Platform
|
|
1186
1191
|
|
|
1187
1192
|
The `platform/` directory contains an optional control plane that hosts agent definitions, dev tokens, and a live debug surface. The SDK works fully without it (`load_local_agent`, `load_builtin_agent` keep their existing semantics) — but with it you get:
|
|
1188
1193
|
|
|
@@ -1301,7 +1306,7 @@ the name from the loaded agent's `.name` attribute — no env var needed.
|
|
|
1301
1306
|
|
|
1302
1307
|
## 👤 User Scope + Roles
|
|
1303
1308
|
|
|
1304
|
-
Real backends serve many users, and different users get different capabilities.
|
|
1309
|
+
Real backends serve many users, and different users get different capabilities. Hexgate splits that into two pieces:
|
|
1305
1310
|
|
|
1306
1311
|
- **`User`** — the per-request scope. Marks "this invocation acts on behalf of alice, in role X." Async context manager; pushes a fact-bearing Biscuit through the agent runtime.
|
|
1307
1312
|
- **Role policies** — one `policy.yaml` per role, optionally inheriting from a base mixin. The runtime picks the right one at call time based on the active `User.role`.
|
|
@@ -1451,3 +1456,7 @@ async for event in stream_agent(agent, handler, "latest AI breakthroughs"):
|
|
|
1451
1456
|
- assistant text deltas
|
|
1452
1457
|
- tool lifecycle
|
|
1453
1458
|
- final run completion
|
|
1459
|
+
|
|
1460
|
+
---
|
|
1461
|
+
|
|
1462
|
+
If Hexgate looks useful, [give it a ⭐ on GitHub](https://github.com/HexamindOrganisation/hexgate) — it helps more than you'd think. Built by [Hexamind](https://hexgate.ai).
|
|
@@ -21,7 +21,7 @@ from hexgate.runtime import User
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class HexgateRunner:
|
|
24
|
-
"""Runner for Google ADK agents with
|
|
24
|
+
"""Runner for Google ADK agents with Hexgate tool policy and observability."""
|
|
25
25
|
|
|
26
26
|
def __init__(
|
|
27
27
|
self,
|
|
@@ -79,16 +79,32 @@ class HexgateRunner:
|
|
|
79
79
|
user: User,
|
|
80
80
|
**kwargs: Any,
|
|
81
81
|
) -> Generator[Any, None, None]:
|
|
82
|
-
"""Run the Google ADK agent synchronously, yielding events.
|
|
82
|
+
"""Run the Google ADK agent synchronously, yielding events.
|
|
83
|
+
|
|
84
|
+
ADK's ``Runner.run`` drives the agent loop in a worker thread whose
|
|
85
|
+
context cannot see our :class:`User` scope, so the tools' enforcers
|
|
86
|
+
lose the active role. We drive ``run_async`` inline on a per-call loop
|
|
87
|
+
instead, keeping execution in this scoped thread.
|
|
88
|
+
"""
|
|
83
89
|
self._setup_observability()
|
|
84
90
|
self._binding.refresh() # per-run policy pull; 304 when unchanged
|
|
85
91
|
with user.sync_scope(), self._propagate(user):
|
|
86
|
-
|
|
92
|
+
agen = self._runner.run_async(
|
|
87
93
|
user_id=user.user_id,
|
|
88
94
|
session_id=user.session_id,
|
|
89
95
|
new_message=new_message,
|
|
90
96
|
**kwargs,
|
|
91
97
|
)
|
|
98
|
+
loop = asyncio.new_event_loop()
|
|
99
|
+
try:
|
|
100
|
+
while True:
|
|
101
|
+
try:
|
|
102
|
+
yield loop.run_until_complete(agen.__anext__())
|
|
103
|
+
except StopAsyncIteration:
|
|
104
|
+
break
|
|
105
|
+
finally:
|
|
106
|
+
loop.run_until_complete(agen.aclose())
|
|
107
|
+
loop.close()
|
|
92
108
|
|
|
93
109
|
async def run_async(
|
|
94
110
|
self,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Proxy around a pre-built ``CompiledStateGraph`` for
|
|
1
|
+
"""Proxy around a pre-built ``CompiledStateGraph`` for Hexgate-aware calls."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -60,7 +60,7 @@ class HexgateLangchainAgent:
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
def _with_callbacks(self, config: RunnableConfig | None) -> RunnableConfig:
|
|
63
|
-
"""Append the
|
|
63
|
+
"""Append the Hexgate callback handler to ``config['callbacks']``."""
|
|
64
64
|
merged: RunnableConfig = dict(config) if config else {}
|
|
65
65
|
callbacks = list(merged.get("callbacks") or [])
|
|
66
66
|
if self._callback_handler not in callbacks:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""BYO-graph entry point: retrofit a pre-built ``CompiledStateGraph`` with
|
|
2
|
-
|
|
2
|
+
Hexgate policy. Tools are mutated in place so the graph keeps its
|
|
3
3
|
references; the returned :class:`HexgateLangchainAgent` opens a User
|
|
4
4
|
scope + Langfuse propagation per call. For the manifest-driven path,
|
|
5
5
|
use :func:`hexgate.enforce_policy` instead.
|
|
@@ -28,7 +28,7 @@ def wrap_langchain_agent(
|
|
|
28
28
|
tools: list[BaseTool],
|
|
29
29
|
api_key: str | None = None,
|
|
30
30
|
) -> HexgateLangchainAgent:
|
|
31
|
-
"""Wrap a pre-built LangGraph agent with
|
|
31
|
+
"""Wrap a pre-built LangGraph agent with Hexgate policy enforcement.
|
|
32
32
|
|
|
33
33
|
Mutates ``tools`` in place so the graph keeps its references.
|
|
34
34
|
The returned proxy takes ``user`` per invocation; role resolves at
|
|
@@ -32,7 +32,7 @@ from hexgate.security.enforcer import build_enforcer
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class HexgateRunner:
|
|
35
|
-
"""Runner for OpenAI agents with
|
|
35
|
+
"""Runner for OpenAI agents with Hexgate tool policy and observability."""
|
|
36
36
|
|
|
37
37
|
def __init__(self, api_key: str | None = None):
|
|
38
38
|
self.api_key = api_key or os.getenv("HEXGATE_KEY")
|
|
@@ -134,21 +134,22 @@ class HexgateRunner:
|
|
|
134
134
|
) -> RunResultStreaming:
|
|
135
135
|
"""Stream the OpenAI agent inside a User scope.
|
|
136
136
|
|
|
137
|
-
``Runner.run_streamed`` returns sync
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
the
|
|
137
|
+
``Runner.run_streamed`` returns sync but spawns the agent loop as a
|
|
138
|
+
background task that snapshots the current contextvars at creation;
|
|
139
|
+
tools fire there, not in ``stream_events``. So the User scope must be
|
|
140
|
+
active around the ``run_streamed`` call for the task to inherit it —
|
|
141
|
+
the wrapped iterator re-opens it for exit/audit semantics.
|
|
142
142
|
"""
|
|
143
143
|
self._setup_observability()
|
|
144
144
|
binding = self._binding_for(agent)
|
|
145
145
|
binding.refresh() # must precede the wrap + setup
|
|
146
146
|
wrapped_agent = wrap_openai_agent(agent, enforcer=binding.enforcer)
|
|
147
147
|
|
|
148
|
-
with
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
with user.sync_scope():
|
|
149
|
+
with self._propagate(user, agent.name):
|
|
150
|
+
result = Runner.run_streamed(
|
|
151
|
+
wrapped_agent, input, run_config=run_config, **kwargs
|
|
152
|
+
)
|
|
152
153
|
|
|
153
154
|
original_stream_events = result.stream_events
|
|
154
155
|
|
|
@@ -49,7 +49,7 @@ def wrap_pydantic_agent(
|
|
|
49
49
|
agent: Agent,
|
|
50
50
|
api_key: str | None = None,
|
|
51
51
|
) -> HexgatePydanticAgent:
|
|
52
|
-
"""Wrap a pydantic_ai agent with
|
|
52
|
+
"""Wrap a pydantic_ai agent with Hexgate policy + observability.
|
|
53
53
|
|
|
54
54
|
Returns a :class:`HexgatePydanticAgent` backed by a clone of the
|
|
55
55
|
caller's ``agent``; the original is not mutated. The proxy takes
|