hexgate 0.2.5__tar.gz → 0.2.6__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.5 → hexgate-0.2.6}/PKG-INFO +90 -29
- {hexgate-0.2.5 → hexgate-0.2.6}/README.md +88 -28
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/google/runner.py +3 -3
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/langchain/wrapper.py +4 -5
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/openai/runner.py +3 -3
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/pydantic_ai/wrapper.py +4 -4
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/factory.py +6 -7
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/loader.py +4 -4
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/audit.py +10 -9
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/bootstrap.py +5 -6
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/_common.py +37 -8
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/chat.py +1 -1
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/register.py +4 -5
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cloud/attenuate.py +1 -1
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cloud/biscuit.py +1 -1
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cloud/client.py +7 -9
- hexgate-0.2.6/hexgate/config/env.py +38 -0
- hexgate-0.2.6/hexgate/config/settings.py +34 -0
- hexgate-0.2.6/hexgate/mcp/__init__.py +47 -0
- hexgate-0.2.6/hexgate/mcp/client.py +170 -0
- hexgate-0.2.6/hexgate/mcp/config.py +93 -0
- hexgate-0.2.6/hexgate/mcp/proxy.py +347 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/binding.py +4 -3
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/enforcer.py +1 -1
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/PKG-INFO +90 -29
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/SOURCES.txt +5 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/requires.txt +1 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/pyproject.toml +6 -1
- {hexgate-0.2.5 → hexgate-0.2.6}/tests/test_bootstrap.py +9 -12
- hexgate-0.2.5/hexgate/config/settings.py +0 -50
- {hexgate-0.2.5 → hexgate-0.2.6}/LICENSE +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/google/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/google/tools.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/google/wrapper.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/langchain/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/langchain/agent.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/langchain/tools.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/openai/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/openai/tools.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/openai/wrapper.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/pydantic_ai/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/pydantic_ai/agent.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/adapters/pydantic_ai/tools.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/builtin/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/builtin/researcher/agent.yaml +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/builtin/researcher/policy.yaml +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/builtin/researcher/system.md +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/models.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/agents/prompts/agent_system.md +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/policy/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/policy/main.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/google.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/hexgate.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/langchain.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/main.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/manifest.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/models.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/openai.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/register/pydantic_ai.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/serve.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cli/state.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/cloud/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/config/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/command_policy.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/context.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/sandbox_runtime.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/srt.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/runtime/workspace.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/bundle.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/constraints.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/decision.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/errors.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/file_scope.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/models.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/policy.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/policy_set.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/rego.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/rego_wasm.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/signing.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/source.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/security/wasm_engine.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/streaming/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/streaming/events.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/streaming/normalize.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/bash.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/decorators.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/fetch.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/_common.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/edit_file.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/glob.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/grep.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/read_file.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/files/write_file.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/refund.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tools/websearch.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tracing/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/tracing/langfuse.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/utils/__init__.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate/utils/retry.py +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/dependency_links.txt +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/entry_points.txt +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/hexgate.egg-info/top_level.txt +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/setup.cfg +0 -0
- {hexgate-0.2.5 → hexgate-0.2.6}/tests/test_demo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexgate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Hexgate — authorization infrastructure for AI agents (agent runtime + cloud client).
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -29,6 +29,7 @@ Requires-Dist: litellm>=1.50
|
|
|
29
29
|
Requires-Dist: openinference-instrumentation-google-adk>=0.1.11
|
|
30
30
|
Requires-Dist: pydantic-ai-slim>=1.88.0
|
|
31
31
|
Requires-Dist: wasmtime>=20.0
|
|
32
|
+
Requires-Dist: mcp>=1.0
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: ipykernel; extra == "dev"
|
|
34
35
|
Requires-Dist: jupyter; extra == "dev"
|
|
@@ -147,11 +148,11 @@ cp .env.sample .env
|
|
|
147
148
|
hexgate chat --agent example_agent
|
|
148
149
|
```
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
Keys the built-in example agent uses (bootstrap no longer hard-requires them — each is only needed if you invoke the piece that reads it):
|
|
151
152
|
|
|
152
|
-
- `OPENAI_API_KEY`
|
|
153
|
-
- `LINKUP_API_KEY`
|
|
154
|
-
- `TAVILY_API_KEY`
|
|
153
|
+
- `OPENAI_API_KEY` — the default `openai:gpt-5.4` model. Skip it if you pass your own model; if it's missing, the model provider raises a key-named error when the agent is built.
|
|
154
|
+
- `LINKUP_API_KEY` — the built-in `web_search` tool, which raises a clear error the first time it runs without a key.
|
|
155
|
+
- `TAVILY_API_KEY` — the built-in `fetch` tool, same as above.
|
|
155
156
|
|
|
156
157
|
Run `hexgate --help` to see all subcommands (`chat`, `serve`, `register`), and `hexgate <subcommand> --help` for the flags each one accepts.
|
|
157
158
|
|
|
@@ -175,7 +176,7 @@ The included local agent lives in `examples/example_agent/`, and the CLI can als
|
|
|
175
176
|
|
|
176
177
|
The two Quick Starts above aren't competing — they answer different questions.
|
|
177
178
|
|
|
178
|
-
**Inner loop — `hexgate chat`.** A single-process REPL against a local or builtin agent. No platform, no Docker, no browser. The chat command sets `HEXGATE_LOCAL_MODE=1` automatically so audit stays on your machine even if `
|
|
179
|
+
**Inner loop — `hexgate chat`.** A single-process REPL against a local or builtin agent. No platform, no Docker, no browser. The chat command sets `HEXGATE_LOCAL_MODE=1` automatically so audit stays on your machine even if `HEXGATE_API_KEY` lives in your `.env` from an earlier session. Denies and approval-required calls render as inline panels in the terminal — same `Decision` data the platform would log, surfaced where you're iterating. Reach for `chat` when you're authoring a policy YAML, tweaking a tool, or shaping a system prompt.
|
|
179
180
|
|
|
180
181
|
**Team loop — `hexgate serve` + dashboard Playground.** Same agent code, but the policy + decisions round-trip through the platform. You get auditable decisions in ClickHouse, the shared Playground UI, and live policy edits via the dashboard. Reach for `serve` when you're collaborating on an agent's behaviour, debugging a production-like trace, or demoing.
|
|
181
182
|
|
|
@@ -203,7 +204,7 @@ make dashboard
|
|
|
203
204
|
|
|
204
205
|
# Terminal 3 — mint a token, then serve your local agent
|
|
205
206
|
# 1. Open http://localhost:5173/tokens, click "Mint new token", copy the value.
|
|
206
|
-
# 2. Add to asianf/.env:
|
|
207
|
+
# 2. Add to asianf/.env: HEXGATE_API_KEY=fty_live_...
|
|
207
208
|
# 3. Pick the agent's Python entrypoint (module:attr — uvicorn-style)
|
|
208
209
|
# and let `hexgate serve` take over:
|
|
209
210
|
make serve # default — examples.customer_bot:agent
|
|
@@ -283,6 +284,22 @@ Integration tests (`pytest -m integration`) round-trip rows through the live Cli
|
|
|
283
284
|
|
|
284
285
|
The dashboard's `/policies` page lets you edit each agent's policy. `hexgate serve` re-fetches at every turn boundary, so your edits take effect on the next chat message without a restart.
|
|
285
286
|
|
|
287
|
+
### Outbound email (Resend)
|
|
288
|
+
|
|
289
|
+
Verification emails and password-reset links go through [Resend](https://resend.com). The platform API has two senders:
|
|
290
|
+
|
|
291
|
+
- **Dev (default)** — `StderrEmailSender` prints the mail body (including the magic link) to stderr. No keys needed; click the link out of the terminal to exercise the flow end-to-end.
|
|
292
|
+
- **Production** — `ResendEmailSender`, picked up automatically when both `RESEND_API_KEY` and `HEXGATE_EMAIL_FROM` are set. The from-address must be on a verified Resend domain.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# platform/api/.env
|
|
296
|
+
RESEND_API_KEY=re_…
|
|
297
|
+
HEXGATE_EMAIL_FROM="Hexgate <noreply@yourdomain.com>"
|
|
298
|
+
HEXGATE_DASHBOARD_URL=https://app.yourdomain.com # ← link host inside the email
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Partial config (only one var set) keeps the dev stderr sender so an operator notices the misconfig in the startup log line instead of silently failing every send at delivery. Resend API failures are logged at error level but never 5xx the calling endpoint — the user can request a new link.
|
|
302
|
+
|
|
286
303
|
## ✨ Core Primitives
|
|
287
304
|
|
|
288
305
|
The two main primitives are:
|
|
@@ -320,7 +337,7 @@ Dev wrote an OpenAI Agents / LangChain / Google ADK / Pydantic AI agent. They wr
|
|
|
320
337
|
from hexgate.adapters.openai import HexgateRunner # or .langchain.wrap_langchain_agent, .google.HexgateRunner, .pydantic_ai.wrap_pydantic_agent
|
|
321
338
|
from hexgate.runtime import User
|
|
322
339
|
|
|
323
|
-
runner = HexgateRunner() # picks up
|
|
340
|
+
runner = HexgateRunner() # picks up HEXGATE_API_KEY from env
|
|
324
341
|
await runner.run(
|
|
325
342
|
my_agent,
|
|
326
343
|
"refund 30",
|
|
@@ -355,8 +372,8 @@ Same enforcement seam, same `User` scope. The difference is whose system of reco
|
|
|
355
372
|
|
|
356
373
|
| What dev sets | What changes |
|
|
357
374
|
|---|---|
|
|
358
|
-
| `
|
|
359
|
-
| `HEXGATE_API_URL=
|
|
375
|
+
| `HEXGATE_API_KEY=fty_live_<project>_…` | Wakes up the platform path. Without it, adapters / `load_agent` fall back to local / builtin. (Renamed from `HEXGATE_KEY`, which is no longer read.) |
|
|
376
|
+
| `HEXGATE_API_URL=https://app.hexgate.ai` *(optional)* | Platform endpoint. Defaults to Hexgate Cloud (`https://app.hexgate.ai`). Set to `http://localhost:8000` only when self-hosting the platform locally — your key must be minted by whichever platform this points at. |
|
|
360
377
|
| `HEXGATE_LOCAL_POLICY=./policy.yaml` *or* `./bundle/` | Dev escape hatch: enforce a policy from disk, hot-reload on save. Wins over the platform's bundle. |
|
|
361
378
|
| `HEXGATE_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` *(optional)* | Sign locally-recompiled yaml so `bundle.is_signed` reads True. |
|
|
362
379
|
| `HEXGATE_BUNDLE_PUBKEY_PATH=./keys/prod.public` *(optional)* | Verify a pre-built bundle dir against this pubkey on every reload. |
|
|
@@ -364,6 +381,11 @@ Same enforcement seam, same `User` scope. The difference is whose system of reco
|
|
|
364
381
|
|
|
365
382
|
No config object to instantiate, no `enforce_policy(...)` call to remember on the platform path. The adapter / loader threads it all through.
|
|
366
383
|
|
|
384
|
+
**Connecting to Hexgate.** The key and the URL are coupled: a `fty_live_…` key only verifies against the platform instance that minted it.
|
|
385
|
+
|
|
386
|
+
- **Hosted (default):** set `HEXGATE_API_KEY` to the key from [app.hexgate.ai](https://app.hexgate.ai). Leave `HEXGATE_API_URL` unset — it defaults to `https://app.hexgate.ai`.
|
|
387
|
+
- **Self-hosted / local platform:** additionally set `HEXGATE_API_URL=http://localhost:8000` (or your host), and use a key minted by *that* platform.
|
|
388
|
+
|
|
367
389
|
### Where enforcement actually happens
|
|
368
390
|
|
|
369
391
|
Walk through one tool call:
|
|
@@ -377,7 +399,7 @@ Walk through one tool call:
|
|
|
377
399
|
`_policy_source` is set automatically by the loader based on env:
|
|
378
400
|
|
|
379
401
|
- `HEXGATE_LOCAL_POLICY` set → `YamlPolicySource` or `BundleDirPolicySource` (mtime-driven refresh)
|
|
380
|
-
- `
|
|
402
|
+
- `HEXGATE_API_KEY` set, no local override → `PlatformPolicySource` (ETag / `304 Not Modified` refresh)
|
|
381
403
|
- Neither → no source attached; enforcement uses whatever was loaded once
|
|
382
404
|
|
|
383
405
|
**Scope of the per-turn refresh:** only the policy bundle. `system_prompt`, the manifest's tool list, and the model id are read once at agent construction and stay fixed for the lifetime of the process. Edit those on the dashboard and the change lands at the next `hexgate serve` restart — not at the next turn. The split is deliberate: policy is the operator's primary lever (and the one that needs to be auditable per-decision), while the manifest is an author-time concept.
|
|
@@ -387,7 +409,7 @@ Walk through one tool call:
|
|
|
387
409
|
1. **Per-call identity stays explicit.** `User` is the one piece the adapter can't infer from env, because it's per-request, not per-process. One line wrapping each call (`user=User(...)` kwarg on adapters, `async with User(...)` for native).
|
|
388
410
|
2. **`approval_required` tools.** If the policy uses that mode, dev decides what happens — pass `approval_handler=` (True / False / callable) when wrapping. Default for `hexgate serve` is auto-approve; for `hexgate chat` it prompts the TTY. Native code gets whatever the dev wires.
|
|
389
411
|
|
|
390
|
-
Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `
|
|
412
|
+
Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `HEXGATE_API_KEY` and wrap, or set `HEXGATE_LOCAL_POLICY` and wrap. That's the surface.
|
|
391
413
|
|
|
392
414
|
## 📦 What You Can Import
|
|
393
415
|
|
|
@@ -430,6 +452,46 @@ from hexgate import (
|
|
|
430
452
|
)
|
|
431
453
|
```
|
|
432
454
|
|
|
455
|
+
## 🔌 MCP servers (proxy)
|
|
456
|
+
|
|
457
|
+
`hexgate.mcp` wraps any [Model Context Protocol](https://modelcontextprotocol.io) server as a set of LangChain tools that flow through the same policy enforcement, audit, and approval pipeline as native `@agent_tool` functions. Zero glue code — `tools/list` runs at connect time and each exposed tool auto-registers under a `mcp-<server>-<tool>` namespace.
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
from hexgate import create_agent, enforce_policy
|
|
461
|
+
from hexgate.mcp import MCPServerConfig, MCPToolset
|
|
462
|
+
|
|
463
|
+
slack = MCPServerConfig(
|
|
464
|
+
name="slack", transport="stdio",
|
|
465
|
+
command="slack-mcp-server",
|
|
466
|
+
env={"SLACK_TOKEN": "..."},
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
async with MCPToolset(slack) as mcp:
|
|
470
|
+
agent, handler = create_agent(model="gpt-5.4", tools=mcp.tools)
|
|
471
|
+
agent = enforce_policy(agent, "policy.yaml")
|
|
472
|
+
await agent.ainvoke({"messages": [...]}, config={"configurable": {}})
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Then in `policy.yaml` reference the MCP tools by qualified name:
|
|
476
|
+
|
|
477
|
+
```yaml
|
|
478
|
+
roles:
|
|
479
|
+
default:
|
|
480
|
+
tools:
|
|
481
|
+
"mcp-slack-list_channels": { mode: allow }
|
|
482
|
+
"mcp-slack-send_message":
|
|
483
|
+
mode: approval_required
|
|
484
|
+
"mcp-github-create_issue":
|
|
485
|
+
mode: allow
|
|
486
|
+
constraints: ["args.repo == 'hexgate'"]
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Hyphens (not colons or dots) because OpenAI Function Calling rejects other separators. Server names must match `^[a-z0-9-]{1,32}$` so qualified names stay under OpenAI's 64-char tool-name limit.
|
|
490
|
+
|
|
491
|
+
Both transports are supported: **stdio** (`command` + `args` + `env` for subprocess MCP servers) and **streamable HTTP** (`url` + `headers` for remote endpoints). The toolset is an async context manager — opening connects + lists every server's catalog; closing tears down transports symmetrically (with cleanup on partial-open failures).
|
|
492
|
+
|
|
493
|
+
**Try it:** `make demo-mcp` runs `examples/mcp_demo.py` — one self-contained file that spawns a tiny FastMCP server, attaches it, and walks through one tool call per policy outcome (allow / deny / approval-required). No external services, no LLM key.
|
|
494
|
+
|
|
433
495
|
## 🤝 Framework Agent Wrapping
|
|
434
496
|
|
|
435
497
|
In addition to its native `create_agent(...)` runtime, `hexgate` ships adapters that wrap agents built with **OpenAI Agents SDK**, **LangChain / LangGraph**, **Google ADK**, or **Pydantic AI** to add two things without touching the agent's logic:
|
|
@@ -449,7 +511,7 @@ The four integrations differ in shape because the underlying SDKs do:
|
|
|
449
511
|
|
|
450
512
|
Role resolution happens **at call time** from the active `User` contextvar — one wrapped agent serves many users concurrently because the scope is per-call. The original agent object is left intact (or, for LangChain BYO-graph tools, mutated by design so the same `tools` list flows through `create_react_agent`); the wrapper holds the policy.
|
|
451
513
|
|
|
452
|
-
All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `
|
|
514
|
+
All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `HEXGATE_API_KEY` environment variable.
|
|
453
515
|
|
|
454
516
|
### OpenAI Agents SDK — `HexgateRunner`
|
|
455
517
|
|
|
@@ -479,7 +541,7 @@ async def main():
|
|
|
479
541
|
model="gpt-4o-mini",
|
|
480
542
|
)
|
|
481
543
|
|
|
482
|
-
runner = HexgateRunner() # picks up
|
|
544
|
+
runner = HexgateRunner() # picks up HEXGATE_API_KEY from env
|
|
483
545
|
result = await runner.run(
|
|
484
546
|
agent,
|
|
485
547
|
"What's the weather in Cherbourg?",
|
|
@@ -539,7 +601,7 @@ async def main():
|
|
|
539
601
|
agent = wrap_langchain_agent(
|
|
540
602
|
agent=graph,
|
|
541
603
|
tools=TOOLS, # same list passed to create_react_agent — wrapped in place
|
|
542
|
-
api_key="sk-...", # or rely on
|
|
604
|
+
api_key="sk-...", # or rely on HEXGATE_API_KEY
|
|
543
605
|
)
|
|
544
606
|
|
|
545
607
|
result = await agent.ainvoke(
|
|
@@ -615,7 +677,7 @@ async def main():
|
|
|
615
677
|
agent=agent,
|
|
616
678
|
app_name="google_runner_example",
|
|
617
679
|
session_service=session_service,
|
|
618
|
-
) # picks up
|
|
680
|
+
) # picks up HEXGATE_API_KEY from env
|
|
619
681
|
|
|
620
682
|
user_msg = types.Content(
|
|
621
683
|
role="user", parts=[types.Part(text="What is the weather in New Delhi?")]
|
|
@@ -667,7 +729,7 @@ async def main():
|
|
|
667
729
|
|
|
668
730
|
agent = wrap_pydantic_agent(
|
|
669
731
|
agent=agent,
|
|
670
|
-
api_key="sk-...", # or rely on
|
|
732
|
+
api_key="sk-...", # or rely on HEXGATE_API_KEY
|
|
671
733
|
)
|
|
672
734
|
|
|
673
735
|
result = await agent.run(
|
|
@@ -1085,13 +1147,12 @@ Worth being explicit about the gaps so operators know where to layer their own c
|
|
|
1085
1147
|
|
|
1086
1148
|
## 🔧 Environment
|
|
1087
1149
|
|
|
1088
|
-
Copy `.env.sample` to `.env
|
|
1150
|
+
Copy `.env.sample` to `.env`. None of these are required to boot — set the ones whose feature you use. A missing key surfaces when that feature runs: the built-in tools raise their own clear error at call time, and the model provider raises a key-named error when the agent is built.
|
|
1089
1151
|
|
|
1090
|
-
- `OPENAI_API_KEY`
|
|
1091
|
-
- `LINKUP_API_KEY`
|
|
1092
|
-
- `TAVILY_API_KEY`
|
|
1093
|
-
- `LANGFUSE_SECRET_KEY`
|
|
1094
|
-
- `LANGFUSE_PUBLIC_KEY`
|
|
1152
|
+
- `OPENAI_API_KEY` — default model
|
|
1153
|
+
- `LINKUP_API_KEY` — built-in `web_search` tool
|
|
1154
|
+
- `TAVILY_API_KEY` — built-in `fetch` tool
|
|
1155
|
+
- `LANGFUSE_SECRET_KEY` / `LANGFUSE_PUBLIC_KEY` — tracing (optional)
|
|
1095
1156
|
- optional `LANGFUSE_HOST`
|
|
1096
1157
|
|
|
1097
1158
|
Policy-bundle enforcement (see [Policy Bundles](#-policy-bundles--compile-sign-enforce-wasm)) reads a few more, all optional:
|
|
@@ -1171,7 +1232,7 @@ Register a code-defined agent's manifest with the Hexgate platform. `--agent`
|
|
|
1171
1232
|
takes a Python import path of the form `module.path:attribute`, the same shape
|
|
1172
1233
|
as ASGI/WSGI entrypoints. The CLI imports the module, grabs the agent object,
|
|
1173
1234
|
and POSTs its manifest to `${HEXGATE_API_URL}/v1/agents` using
|
|
1174
|
-
`${
|
|
1235
|
+
`${HEXGATE_API_KEY}` as the bearer token:
|
|
1175
1236
|
|
|
1176
1237
|
```bash
|
|
1177
1238
|
hexgate register --agent examples.customer_bot:agent
|
|
@@ -1313,8 +1374,8 @@ Bridges your local agent runtime to the dashboard via the platform's WebSocket r
|
|
|
1313
1374
|
|
|
1314
1375
|
```bash
|
|
1315
1376
|
# in asianf/.env
|
|
1316
|
-
|
|
1317
|
-
HEXGATE_API_URL=http://localhost:8000 # optional
|
|
1377
|
+
HEXGATE_API_KEY=fty_live_<project>_<biscuit>
|
|
1378
|
+
HEXGATE_API_URL=http://localhost:8000 # optional; only when self-hosting the platform locally
|
|
1318
1379
|
|
|
1319
1380
|
# pick an agent module:attr — uvicorn-style spec
|
|
1320
1381
|
uv run hexgate serve examples.customer_bot:agent
|
|
@@ -1353,7 +1414,7 @@ There's no longer a `HEXGATE_AGENT_NAME` env var, `--agent` flag, or
|
|
|
1353
1414
|
`--use` flag — the spec carries everything. If you've been setting
|
|
1354
1415
|
`HEXGATE_AGENT_NAME` in `.env`, drop it.
|
|
1355
1416
|
|
|
1356
|
-
### How `load_agent()` resolves with `
|
|
1417
|
+
### How `load_agent()` resolves with `HEXGATE_API_KEY`
|
|
1357
1418
|
|
|
1358
1419
|
```python
|
|
1359
1420
|
from hexgate import load_agent
|
|
@@ -1361,8 +1422,8 @@ from hexgate import load_agent
|
|
|
1361
1422
|
agent, handler = load_agent("read_only") # explicit name required
|
|
1362
1423
|
```
|
|
1363
1424
|
|
|
1364
|
-
When `
|
|
1365
|
-
the platform (via `load_hexgate_agent`). When `
|
|
1425
|
+
When `HEXGATE_API_KEY` is set, `load_agent(name)` fetches the named agent from
|
|
1426
|
+
the platform (via `load_hexgate_agent`). When `HEXGATE_API_KEY` is not set, it
|
|
1366
1427
|
falls back to local / registered / builtin resolution — no platform call.
|
|
1367
1428
|
|
|
1368
1429
|
The legacy `HEXGATE_AGENT_NAME` env-var fallback was removed in Phase 7;
|
|
@@ -107,11 +107,11 @@ cp .env.sample .env
|
|
|
107
107
|
hexgate chat --agent example_agent
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
Keys the built-in example agent uses (bootstrap no longer hard-requires them — each is only needed if you invoke the piece that reads it):
|
|
111
111
|
|
|
112
|
-
- `OPENAI_API_KEY`
|
|
113
|
-
- `LINKUP_API_KEY`
|
|
114
|
-
- `TAVILY_API_KEY`
|
|
112
|
+
- `OPENAI_API_KEY` — the default `openai:gpt-5.4` model. Skip it if you pass your own model; if it's missing, the model provider raises a key-named error when the agent is built.
|
|
113
|
+
- `LINKUP_API_KEY` — the built-in `web_search` tool, which raises a clear error the first time it runs without a key.
|
|
114
|
+
- `TAVILY_API_KEY` — the built-in `fetch` tool, same as above.
|
|
115
115
|
|
|
116
116
|
Run `hexgate --help` to see all subcommands (`chat`, `serve`, `register`), and `hexgate <subcommand> --help` for the flags each one accepts.
|
|
117
117
|
|
|
@@ -135,7 +135,7 @@ The included local agent lives in `examples/example_agent/`, and the CLI can als
|
|
|
135
135
|
|
|
136
136
|
The two Quick Starts above aren't competing — they answer different questions.
|
|
137
137
|
|
|
138
|
-
**Inner loop — `hexgate chat`.** A single-process REPL against a local or builtin agent. No platform, no Docker, no browser. The chat command sets `HEXGATE_LOCAL_MODE=1` automatically so audit stays on your machine even if `
|
|
138
|
+
**Inner loop — `hexgate chat`.** A single-process REPL against a local or builtin agent. No platform, no Docker, no browser. The chat command sets `HEXGATE_LOCAL_MODE=1` automatically so audit stays on your machine even if `HEXGATE_API_KEY` lives in your `.env` from an earlier session. Denies and approval-required calls render as inline panels in the terminal — same `Decision` data the platform would log, surfaced where you're iterating. Reach for `chat` when you're authoring a policy YAML, tweaking a tool, or shaping a system prompt.
|
|
139
139
|
|
|
140
140
|
**Team loop — `hexgate serve` + dashboard Playground.** Same agent code, but the policy + decisions round-trip through the platform. You get auditable decisions in ClickHouse, the shared Playground UI, and live policy edits via the dashboard. Reach for `serve` when you're collaborating on an agent's behaviour, debugging a production-like trace, or demoing.
|
|
141
141
|
|
|
@@ -163,7 +163,7 @@ make dashboard
|
|
|
163
163
|
|
|
164
164
|
# Terminal 3 — mint a token, then serve your local agent
|
|
165
165
|
# 1. Open http://localhost:5173/tokens, click "Mint new token", copy the value.
|
|
166
|
-
# 2. Add to asianf/.env:
|
|
166
|
+
# 2. Add to asianf/.env: HEXGATE_API_KEY=fty_live_...
|
|
167
167
|
# 3. Pick the agent's Python entrypoint (module:attr — uvicorn-style)
|
|
168
168
|
# and let `hexgate serve` take over:
|
|
169
169
|
make serve # default — examples.customer_bot:agent
|
|
@@ -243,6 +243,22 @@ Integration tests (`pytest -m integration`) round-trip rows through the live Cli
|
|
|
243
243
|
|
|
244
244
|
The dashboard's `/policies` page lets you edit each agent's policy. `hexgate serve` re-fetches at every turn boundary, so your edits take effect on the next chat message without a restart.
|
|
245
245
|
|
|
246
|
+
### Outbound email (Resend)
|
|
247
|
+
|
|
248
|
+
Verification emails and password-reset links go through [Resend](https://resend.com). The platform API has two senders:
|
|
249
|
+
|
|
250
|
+
- **Dev (default)** — `StderrEmailSender` prints the mail body (including the magic link) to stderr. No keys needed; click the link out of the terminal to exercise the flow end-to-end.
|
|
251
|
+
- **Production** — `ResendEmailSender`, picked up automatically when both `RESEND_API_KEY` and `HEXGATE_EMAIL_FROM` are set. The from-address must be on a verified Resend domain.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# platform/api/.env
|
|
255
|
+
RESEND_API_KEY=re_…
|
|
256
|
+
HEXGATE_EMAIL_FROM="Hexgate <noreply@yourdomain.com>"
|
|
257
|
+
HEXGATE_DASHBOARD_URL=https://app.yourdomain.com # ← link host inside the email
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Partial config (only one var set) keeps the dev stderr sender so an operator notices the misconfig in the startup log line instead of silently failing every send at delivery. Resend API failures are logged at error level but never 5xx the calling endpoint — the user can request a new link.
|
|
261
|
+
|
|
246
262
|
## ✨ Core Primitives
|
|
247
263
|
|
|
248
264
|
The two main primitives are:
|
|
@@ -280,7 +296,7 @@ Dev wrote an OpenAI Agents / LangChain / Google ADK / Pydantic AI agent. They wr
|
|
|
280
296
|
from hexgate.adapters.openai import HexgateRunner # or .langchain.wrap_langchain_agent, .google.HexgateRunner, .pydantic_ai.wrap_pydantic_agent
|
|
281
297
|
from hexgate.runtime import User
|
|
282
298
|
|
|
283
|
-
runner = HexgateRunner() # picks up
|
|
299
|
+
runner = HexgateRunner() # picks up HEXGATE_API_KEY from env
|
|
284
300
|
await runner.run(
|
|
285
301
|
my_agent,
|
|
286
302
|
"refund 30",
|
|
@@ -315,8 +331,8 @@ Same enforcement seam, same `User` scope. The difference is whose system of reco
|
|
|
315
331
|
|
|
316
332
|
| What dev sets | What changes |
|
|
317
333
|
|---|---|
|
|
318
|
-
| `
|
|
319
|
-
| `HEXGATE_API_URL=
|
|
334
|
+
| `HEXGATE_API_KEY=fty_live_<project>_…` | Wakes up the platform path. Without it, adapters / `load_agent` fall back to local / builtin. (Renamed from `HEXGATE_KEY`, which is no longer read.) |
|
|
335
|
+
| `HEXGATE_API_URL=https://app.hexgate.ai` *(optional)* | Platform endpoint. Defaults to Hexgate Cloud (`https://app.hexgate.ai`). Set to `http://localhost:8000` only when self-hosting the platform locally — your key must be minted by whichever platform this points at. |
|
|
320
336
|
| `HEXGATE_LOCAL_POLICY=./policy.yaml` *or* `./bundle/` | Dev escape hatch: enforce a policy from disk, hot-reload on save. Wins over the platform's bundle. |
|
|
321
337
|
| `HEXGATE_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` *(optional)* | Sign locally-recompiled yaml so `bundle.is_signed` reads True. |
|
|
322
338
|
| `HEXGATE_BUNDLE_PUBKEY_PATH=./keys/prod.public` *(optional)* | Verify a pre-built bundle dir against this pubkey on every reload. |
|
|
@@ -324,6 +340,11 @@ Same enforcement seam, same `User` scope. The difference is whose system of reco
|
|
|
324
340
|
|
|
325
341
|
No config object to instantiate, no `enforce_policy(...)` call to remember on the platform path. The adapter / loader threads it all through.
|
|
326
342
|
|
|
343
|
+
**Connecting to Hexgate.** The key and the URL are coupled: a `fty_live_…` key only verifies against the platform instance that minted it.
|
|
344
|
+
|
|
345
|
+
- **Hosted (default):** set `HEXGATE_API_KEY` to the key from [app.hexgate.ai](https://app.hexgate.ai). Leave `HEXGATE_API_URL` unset — it defaults to `https://app.hexgate.ai`.
|
|
346
|
+
- **Self-hosted / local platform:** additionally set `HEXGATE_API_URL=http://localhost:8000` (or your host), and use a key minted by *that* platform.
|
|
347
|
+
|
|
327
348
|
### Where enforcement actually happens
|
|
328
349
|
|
|
329
350
|
Walk through one tool call:
|
|
@@ -337,7 +358,7 @@ Walk through one tool call:
|
|
|
337
358
|
`_policy_source` is set automatically by the loader based on env:
|
|
338
359
|
|
|
339
360
|
- `HEXGATE_LOCAL_POLICY` set → `YamlPolicySource` or `BundleDirPolicySource` (mtime-driven refresh)
|
|
340
|
-
- `
|
|
361
|
+
- `HEXGATE_API_KEY` set, no local override → `PlatformPolicySource` (ETag / `304 Not Modified` refresh)
|
|
341
362
|
- Neither → no source attached; enforcement uses whatever was loaded once
|
|
342
363
|
|
|
343
364
|
**Scope of the per-turn refresh:** only the policy bundle. `system_prompt`, the manifest's tool list, and the model id are read once at agent construction and stay fixed for the lifetime of the process. Edit those on the dashboard and the change lands at the next `hexgate serve` restart — not at the next turn. The split is deliberate: policy is the operator's primary lever (and the one that needs to be auditable per-decision), while the manifest is an author-time concept.
|
|
@@ -347,7 +368,7 @@ Walk through one tool call:
|
|
|
347
368
|
1. **Per-call identity stays explicit.** `User` is the one piece the adapter can't infer from env, because it's per-request, not per-process. One line wrapping each call (`user=User(...)` kwarg on adapters, `async with User(...)` for native).
|
|
348
369
|
2. **`approval_required` tools.** If the policy uses that mode, dev decides what happens — pass `approval_handler=` (True / False / callable) when wrapping. Default for `hexgate serve` is auto-approve; for `hexgate chat` it prompts the TTY. Native code gets whatever the dev wires.
|
|
349
370
|
|
|
350
|
-
Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `
|
|
371
|
+
Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `HEXGATE_API_KEY` and wrap, or set `HEXGATE_LOCAL_POLICY` and wrap. That's the surface.
|
|
351
372
|
|
|
352
373
|
## 📦 What You Can Import
|
|
353
374
|
|
|
@@ -390,6 +411,46 @@ from hexgate import (
|
|
|
390
411
|
)
|
|
391
412
|
```
|
|
392
413
|
|
|
414
|
+
## 🔌 MCP servers (proxy)
|
|
415
|
+
|
|
416
|
+
`hexgate.mcp` wraps any [Model Context Protocol](https://modelcontextprotocol.io) server as a set of LangChain tools that flow through the same policy enforcement, audit, and approval pipeline as native `@agent_tool` functions. Zero glue code — `tools/list` runs at connect time and each exposed tool auto-registers under a `mcp-<server>-<tool>` namespace.
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from hexgate import create_agent, enforce_policy
|
|
420
|
+
from hexgate.mcp import MCPServerConfig, MCPToolset
|
|
421
|
+
|
|
422
|
+
slack = MCPServerConfig(
|
|
423
|
+
name="slack", transport="stdio",
|
|
424
|
+
command="slack-mcp-server",
|
|
425
|
+
env={"SLACK_TOKEN": "..."},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
async with MCPToolset(slack) as mcp:
|
|
429
|
+
agent, handler = create_agent(model="gpt-5.4", tools=mcp.tools)
|
|
430
|
+
agent = enforce_policy(agent, "policy.yaml")
|
|
431
|
+
await agent.ainvoke({"messages": [...]}, config={"configurable": {}})
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Then in `policy.yaml` reference the MCP tools by qualified name:
|
|
435
|
+
|
|
436
|
+
```yaml
|
|
437
|
+
roles:
|
|
438
|
+
default:
|
|
439
|
+
tools:
|
|
440
|
+
"mcp-slack-list_channels": { mode: allow }
|
|
441
|
+
"mcp-slack-send_message":
|
|
442
|
+
mode: approval_required
|
|
443
|
+
"mcp-github-create_issue":
|
|
444
|
+
mode: allow
|
|
445
|
+
constraints: ["args.repo == 'hexgate'"]
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Hyphens (not colons or dots) because OpenAI Function Calling rejects other separators. Server names must match `^[a-z0-9-]{1,32}$` so qualified names stay under OpenAI's 64-char tool-name limit.
|
|
449
|
+
|
|
450
|
+
Both transports are supported: **stdio** (`command` + `args` + `env` for subprocess MCP servers) and **streamable HTTP** (`url` + `headers` for remote endpoints). The toolset is an async context manager — opening connects + lists every server's catalog; closing tears down transports symmetrically (with cleanup on partial-open failures).
|
|
451
|
+
|
|
452
|
+
**Try it:** `make demo-mcp` runs `examples/mcp_demo.py` — one self-contained file that spawns a tiny FastMCP server, attaches it, and walks through one tool call per policy outcome (allow / deny / approval-required). No external services, no LLM key.
|
|
453
|
+
|
|
393
454
|
## 🤝 Framework Agent Wrapping
|
|
394
455
|
|
|
395
456
|
In addition to its native `create_agent(...)` runtime, `hexgate` ships adapters that wrap agents built with **OpenAI Agents SDK**, **LangChain / LangGraph**, **Google ADK**, or **Pydantic AI** to add two things without touching the agent's logic:
|
|
@@ -409,7 +470,7 @@ The four integrations differ in shape because the underlying SDKs do:
|
|
|
409
470
|
|
|
410
471
|
Role resolution happens **at call time** from the active `User` contextvar — one wrapped agent serves many users concurrently because the scope is per-call. The original agent object is left intact (or, for LangChain BYO-graph tools, mutated by design so the same `tools` list flows through `create_react_agent`); the wrapper holds the policy.
|
|
411
472
|
|
|
412
|
-
All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `
|
|
473
|
+
All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `HEXGATE_API_KEY` environment variable.
|
|
413
474
|
|
|
414
475
|
### OpenAI Agents SDK — `HexgateRunner`
|
|
415
476
|
|
|
@@ -439,7 +500,7 @@ async def main():
|
|
|
439
500
|
model="gpt-4o-mini",
|
|
440
501
|
)
|
|
441
502
|
|
|
442
|
-
runner = HexgateRunner() # picks up
|
|
503
|
+
runner = HexgateRunner() # picks up HEXGATE_API_KEY from env
|
|
443
504
|
result = await runner.run(
|
|
444
505
|
agent,
|
|
445
506
|
"What's the weather in Cherbourg?",
|
|
@@ -499,7 +560,7 @@ async def main():
|
|
|
499
560
|
agent = wrap_langchain_agent(
|
|
500
561
|
agent=graph,
|
|
501
562
|
tools=TOOLS, # same list passed to create_react_agent — wrapped in place
|
|
502
|
-
api_key="sk-...", # or rely on
|
|
563
|
+
api_key="sk-...", # or rely on HEXGATE_API_KEY
|
|
503
564
|
)
|
|
504
565
|
|
|
505
566
|
result = await agent.ainvoke(
|
|
@@ -575,7 +636,7 @@ async def main():
|
|
|
575
636
|
agent=agent,
|
|
576
637
|
app_name="google_runner_example",
|
|
577
638
|
session_service=session_service,
|
|
578
|
-
) # picks up
|
|
639
|
+
) # picks up HEXGATE_API_KEY from env
|
|
579
640
|
|
|
580
641
|
user_msg = types.Content(
|
|
581
642
|
role="user", parts=[types.Part(text="What is the weather in New Delhi?")]
|
|
@@ -627,7 +688,7 @@ async def main():
|
|
|
627
688
|
|
|
628
689
|
agent = wrap_pydantic_agent(
|
|
629
690
|
agent=agent,
|
|
630
|
-
api_key="sk-...", # or rely on
|
|
691
|
+
api_key="sk-...", # or rely on HEXGATE_API_KEY
|
|
631
692
|
)
|
|
632
693
|
|
|
633
694
|
result = await agent.run(
|
|
@@ -1045,13 +1106,12 @@ Worth being explicit about the gaps so operators know where to layer their own c
|
|
|
1045
1106
|
|
|
1046
1107
|
## 🔧 Environment
|
|
1047
1108
|
|
|
1048
|
-
Copy `.env.sample` to `.env
|
|
1109
|
+
Copy `.env.sample` to `.env`. None of these are required to boot — set the ones whose feature you use. A missing key surfaces when that feature runs: the built-in tools raise their own clear error at call time, and the model provider raises a key-named error when the agent is built.
|
|
1049
1110
|
|
|
1050
|
-
- `OPENAI_API_KEY`
|
|
1051
|
-
- `LINKUP_API_KEY`
|
|
1052
|
-
- `TAVILY_API_KEY`
|
|
1053
|
-
- `LANGFUSE_SECRET_KEY`
|
|
1054
|
-
- `LANGFUSE_PUBLIC_KEY`
|
|
1111
|
+
- `OPENAI_API_KEY` — default model
|
|
1112
|
+
- `LINKUP_API_KEY` — built-in `web_search` tool
|
|
1113
|
+
- `TAVILY_API_KEY` — built-in `fetch` tool
|
|
1114
|
+
- `LANGFUSE_SECRET_KEY` / `LANGFUSE_PUBLIC_KEY` — tracing (optional)
|
|
1055
1115
|
- optional `LANGFUSE_HOST`
|
|
1056
1116
|
|
|
1057
1117
|
Policy-bundle enforcement (see [Policy Bundles](#-policy-bundles--compile-sign-enforce-wasm)) reads a few more, all optional:
|
|
@@ -1131,7 +1191,7 @@ Register a code-defined agent's manifest with the Hexgate platform. `--agent`
|
|
|
1131
1191
|
takes a Python import path of the form `module.path:attribute`, the same shape
|
|
1132
1192
|
as ASGI/WSGI entrypoints. The CLI imports the module, grabs the agent object,
|
|
1133
1193
|
and POSTs its manifest to `${HEXGATE_API_URL}/v1/agents` using
|
|
1134
|
-
`${
|
|
1194
|
+
`${HEXGATE_API_KEY}` as the bearer token:
|
|
1135
1195
|
|
|
1136
1196
|
```bash
|
|
1137
1197
|
hexgate register --agent examples.customer_bot:agent
|
|
@@ -1273,8 +1333,8 @@ Bridges your local agent runtime to the dashboard via the platform's WebSocket r
|
|
|
1273
1333
|
|
|
1274
1334
|
```bash
|
|
1275
1335
|
# in asianf/.env
|
|
1276
|
-
|
|
1277
|
-
HEXGATE_API_URL=http://localhost:8000 # optional
|
|
1336
|
+
HEXGATE_API_KEY=fty_live_<project>_<biscuit>
|
|
1337
|
+
HEXGATE_API_URL=http://localhost:8000 # optional; only when self-hosting the platform locally
|
|
1278
1338
|
|
|
1279
1339
|
# pick an agent module:attr — uvicorn-style spec
|
|
1280
1340
|
uv run hexgate serve examples.customer_bot:agent
|
|
@@ -1313,7 +1373,7 @@ There's no longer a `HEXGATE_AGENT_NAME` env var, `--agent` flag, or
|
|
|
1313
1373
|
`--use` flag — the spec carries everything. If you've been setting
|
|
1314
1374
|
`HEXGATE_AGENT_NAME` in `.env`, drop it.
|
|
1315
1375
|
|
|
1316
|
-
### How `load_agent()` resolves with `
|
|
1376
|
+
### How `load_agent()` resolves with `HEXGATE_API_KEY`
|
|
1317
1377
|
|
|
1318
1378
|
```python
|
|
1319
1379
|
from hexgate import load_agent
|
|
@@ -1321,8 +1381,8 @@ from hexgate import load_agent
|
|
|
1321
1381
|
agent, handler = load_agent("read_only") # explicit name required
|
|
1322
1382
|
```
|
|
1323
1383
|
|
|
1324
|
-
When `
|
|
1325
|
-
the platform (via `load_hexgate_agent`). When `
|
|
1384
|
+
When `HEXGATE_API_KEY` is set, `load_agent(name)` fetches the named agent from
|
|
1385
|
+
the platform (via `load_hexgate_agent`). When `HEXGATE_API_KEY` is not set, it
|
|
1326
1386
|
falls back to local / registered / builtin resolution — no platform call.
|
|
1327
1387
|
|
|
1328
1388
|
The legacy `HEXGATE_AGENT_NAME` env-var fallback was removed in Phase 7;
|
|
@@ -4,7 +4,6 @@ active role. Langfuse propagation mirrors User identity into spans.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
import os
|
|
8
7
|
from contextlib import contextmanager
|
|
9
8
|
from typing import Any, AsyncGenerator, Generator
|
|
10
9
|
|
|
@@ -17,6 +16,7 @@ from langfuse import get_client, propagate_attributes
|
|
|
17
16
|
from openinference.instrumentation.google_adk import GoogleADKInstrumentor
|
|
18
17
|
|
|
19
18
|
from hexgate.adapters.google.wrapper import wrap_google_agent
|
|
19
|
+
from hexgate.config.env import resolve_api_key
|
|
20
20
|
from hexgate.runtime import User
|
|
21
21
|
|
|
22
22
|
|
|
@@ -32,10 +32,10 @@ class HexgateRunner:
|
|
|
32
32
|
api_key: str | None = None,
|
|
33
33
|
**runner_kwargs: Any,
|
|
34
34
|
):
|
|
35
|
-
self.api_key = api_key
|
|
35
|
+
self.api_key = resolve_api_key(api_key)
|
|
36
36
|
if self.api_key is None:
|
|
37
37
|
raise ValueError(
|
|
38
|
-
"
|
|
38
|
+
"HEXGATE_API_KEY is not set. Pass api_key= explicitly or set the HEXGATE_API_KEY environment variable."
|
|
39
39
|
)
|
|
40
40
|
# Policy resolves at construction (the loud-failure point); the
|
|
41
41
|
# Runner is built once — refresh swaps the enforcer's policy
|
|
@@ -11,13 +11,12 @@ proxy at the top of every call.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
-
import os
|
|
15
|
-
|
|
16
14
|
from langchain_core.tools import BaseTool
|
|
17
15
|
from langgraph.graph.state import CompiledStateGraph
|
|
18
16
|
|
|
19
17
|
from hexgate.adapters.langchain.agent import HexgateLangchainAgent
|
|
20
18
|
from hexgate.adapters.langchain.tools import install_enforcer_on_tools
|
|
19
|
+
from hexgate.config.env import resolve_api_key
|
|
21
20
|
from hexgate.security.binding import PolicyBinding, resolve_policy
|
|
22
21
|
from hexgate.security.enforcer import build_enforcer
|
|
23
22
|
|
|
@@ -33,14 +32,14 @@ def wrap_langchain_agent(
|
|
|
33
32
|
Mutates ``tools`` in place so the graph keeps its references.
|
|
34
33
|
The returned proxy takes ``user`` per invocation; role resolves at
|
|
35
34
|
call time from the active :class:`User`. ``api_key`` falls back to
|
|
36
|
-
``
|
|
35
|
+
``HEXGATE_API_KEY``. ``NEEDS_APPROVAL`` outcomes render as structured
|
|
37
36
|
errors — wire any host-side approval flow outside the SDK. The
|
|
38
37
|
enforced policy is the platform's; unlisted tools are denied.
|
|
39
38
|
"""
|
|
40
|
-
resolved_key = api_key
|
|
39
|
+
resolved_key = resolve_api_key(api_key)
|
|
41
40
|
if not resolved_key:
|
|
42
41
|
raise ValueError(
|
|
43
|
-
"No API key provided. Pass api_key= explicitly or set
|
|
42
|
+
"No API key provided. Pass api_key= explicitly or set the HEXGATE_API_KEY environment variable."
|
|
44
43
|
)
|
|
45
44
|
|
|
46
45
|
agent_name = getattr(agent, "name", "default")
|
|
@@ -8,7 +8,6 @@ enforcer, so a refresh swap reaches every clone.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
-
import os
|
|
12
11
|
from contextlib import contextmanager
|
|
13
12
|
|
|
14
13
|
import nest_asyncio
|
|
@@ -26,6 +25,7 @@ from langfuse import get_client, propagate_attributes
|
|
|
26
25
|
from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor
|
|
27
26
|
|
|
28
27
|
from hexgate.adapters.openai.wrapper import wrap_openai_agent
|
|
28
|
+
from hexgate.config.env import resolve_api_key
|
|
29
29
|
from hexgate.runtime import User
|
|
30
30
|
from hexgate.security.binding import PolicyBinding, resolve_policy
|
|
31
31
|
from hexgate.security.enforcer import build_enforcer
|
|
@@ -35,10 +35,10 @@ class HexgateRunner:
|
|
|
35
35
|
"""Runner for OpenAI agents with Hexgate tool policy and observability."""
|
|
36
36
|
|
|
37
37
|
def __init__(self, api_key: str | None = None):
|
|
38
|
-
self.api_key = api_key
|
|
38
|
+
self.api_key = resolve_api_key(api_key)
|
|
39
39
|
if self.api_key is None:
|
|
40
40
|
raise ValueError(
|
|
41
|
-
"
|
|
41
|
+
"HEXGATE_API_KEY is not set. Pass api_key= explicitly or set the HEXGATE_API_KEY environment variable."
|
|
42
42
|
)
|
|
43
43
|
# Cached per agent name — keeps the ETag memory alive across runs.
|
|
44
44
|
self._bindings: dict[str, PolicyBinding] = {}
|
|
@@ -11,13 +11,13 @@ proxy at the top of every run.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import copy
|
|
14
|
-
import os
|
|
15
14
|
|
|
16
15
|
from pydantic_ai import Agent
|
|
17
16
|
from pydantic_ai.tools import Tool
|
|
18
17
|
|
|
19
18
|
from hexgate.adapters.pydantic_ai.agent import HexgatePydanticAgent
|
|
20
19
|
from hexgate.adapters.pydantic_ai.tools import wrap_tools
|
|
20
|
+
from hexgate.config.env import resolve_api_key
|
|
21
21
|
from hexgate.security.binding import PolicyBinding, resolve_policy
|
|
22
22
|
from hexgate.security.enforcer import build_enforcer
|
|
23
23
|
|
|
@@ -56,13 +56,13 @@ def wrap_pydantic_agent(
|
|
|
56
56
|
``user`` per call; role resolves at call time from the active
|
|
57
57
|
:class:`User`. ``NEEDS_APPROVAL`` raises :class:`ModelRetry` with
|
|
58
58
|
an ``[approval_required]`` marker. ``api_key`` falls back to
|
|
59
|
-
``
|
|
59
|
+
``HEXGATE_API_KEY``. The enforced policy is the platform's; unlisted
|
|
60
60
|
tools are denied.
|
|
61
61
|
"""
|
|
62
|
-
resolved_key = api_key
|
|
62
|
+
resolved_key = resolve_api_key(api_key)
|
|
63
63
|
if not resolved_key:
|
|
64
64
|
raise ValueError(
|
|
65
|
-
"No API key provided. Pass api_key= explicitly or set
|
|
65
|
+
"No API key provided. Pass api_key= explicitly or set the HEXGATE_API_KEY environment variable."
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
agent_name = getattr(agent, "name", None) or "default"
|