hexgate 0.1.2__tar.gz → 0.2.1__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.
Files changed (122) hide show
  1. {hexgate-0.1.2 → hexgate-0.2.1}/PKG-INFO +174 -158
  2. {hexgate-0.1.2 → hexgate-0.2.1}/README.md +172 -156
  3. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/__init__.py +14 -14
  4. hexgate-0.2.1/hexgate/adapters/google/__init__.py +3 -0
  5. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/google/runner.py +6 -6
  6. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/google/tools.py +1 -1
  7. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/google/wrapper.py +6 -6
  8. hexgate-0.2.1/hexgate/adapters/langchain/__init__.py +7 -0
  9. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/langchain/agent.py +5 -5
  10. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/langchain/tools.py +9 -9
  11. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/langchain/wrapper.py +14 -14
  12. hexgate-0.2.1/hexgate/adapters/openai/__init__.py +3 -0
  13. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/openai/runner.py +9 -9
  14. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/openai/tools.py +1 -1
  15. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/openai/wrapper.py +2 -2
  16. hexgate-0.2.1/hexgate/adapters/pydantic_ai/__init__.py +7 -0
  17. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/pydantic_ai/agent.py +4 -4
  18. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/pydantic_ai/tools.py +1 -1
  19. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/pydantic_ai/wrapper.py +13 -13
  20. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/__init__.py +5 -5
  21. hexgate-0.2.1/hexgate/agents/builtin/__init__.py +1 -0
  22. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/factory.py +94 -67
  23. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/loader.py +145 -48
  24. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/audit.py +58 -9
  25. hexgate-0.2.1/hexgate/bootstrap.py +62 -0
  26. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/__init__.py +8 -8
  27. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/_common.py +136 -38
  28. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/chat.py +101 -18
  29. hexgate-0.2.1/hexgate/cli/policy/__init__.py +17 -0
  30. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/policy/main.py +11 -11
  31. hexgate-0.2.1/hexgate/cli/register/__init__.py +8 -0
  32. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/google.py +1 -1
  33. hexgate-0.1.2/fortify/cli/register/fortify.py → hexgate-0.2.1/hexgate/cli/register/hexgate.py +10 -10
  34. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/langchain.py +1 -1
  35. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/main.py +10 -10
  36. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/manifest.py +9 -9
  37. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/models.py +4 -4
  38. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/openai.py +1 -1
  39. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/pydantic_ai.py +2 -2
  40. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/register/register.py +6 -6
  41. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/serve.py +25 -25
  42. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cli/state.py +2 -2
  43. hexgate-0.2.1/hexgate/cloud/__init__.py +32 -0
  44. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cloud/attenuate.py +4 -4
  45. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cloud/biscuit.py +2 -2
  46. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/cloud/client.py +49 -39
  47. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/config/settings.py +3 -3
  48. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/__init__.py +5 -5
  49. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/context.py +4 -4
  50. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/workspace.py +5 -5
  51. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/__init__.py +13 -13
  52. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/binding.py +25 -19
  53. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/bundle.py +14 -14
  54. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/constraints.py +1 -1
  55. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/decision.py +3 -3
  56. hexgate-0.2.1/hexgate/security/enforcer.py +136 -0
  57. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/file_scope.py +1 -1
  58. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/models.py +1 -1
  59. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/policy.py +10 -10
  60. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/policy_set.py +4 -4
  61. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/rego.py +11 -11
  62. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/rego_wasm.py +8 -8
  63. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/signing.py +1 -1
  64. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/source.py +41 -41
  65. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/wasm_engine.py +8 -8
  66. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/streaming/__init__.py +3 -3
  67. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/streaming/events.py +1 -1
  68. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/streaming/normalize.py +1 -1
  69. hexgate-0.2.1/hexgate/tools/__init__.py +21 -0
  70. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/bash.py +3 -3
  71. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/decorators.py +3 -3
  72. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/fetch.py +1 -1
  73. hexgate-0.2.1/hexgate/tools/files/__init__.py +9 -0
  74. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/_common.py +1 -1
  75. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/edit_file.py +3 -3
  76. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/glob.py +3 -3
  77. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/grep.py +3 -3
  78. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/read_file.py +3 -3
  79. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/files/write_file.py +3 -3
  80. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/refund.py +1 -1
  81. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tools/websearch.py +1 -1
  82. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tracing/langfuse.py +2 -2
  83. {hexgate-0.1.2 → hexgate-0.2.1}/hexgate.egg-info/PKG-INFO +174 -158
  84. hexgate-0.2.1/hexgate.egg-info/SOURCES.txt +104 -0
  85. hexgate-0.2.1/hexgate.egg-info/entry_points.txt +2 -0
  86. hexgate-0.2.1/hexgate.egg-info/top_level.txt +1 -0
  87. {hexgate-0.1.2 → hexgate-0.2.1}/pyproject.toml +10 -10
  88. hexgate-0.2.1/tests/test_bootstrap.py +136 -0
  89. {hexgate-0.1.2 → hexgate-0.2.1}/tests/test_demo.py +1 -1
  90. hexgate-0.1.2/fortify/adapters/google/__init__.py +0 -3
  91. hexgate-0.1.2/fortify/adapters/langchain/__init__.py +0 -7
  92. hexgate-0.1.2/fortify/adapters/openai/__init__.py +0 -3
  93. hexgate-0.1.2/fortify/adapters/pydantic_ai/__init__.py +0 -7
  94. hexgate-0.1.2/fortify/agents/builtin/__init__.py +0 -1
  95. hexgate-0.1.2/fortify/bootstrap.py +0 -37
  96. hexgate-0.1.2/fortify/cli/policy/__init__.py +0 -17
  97. hexgate-0.1.2/fortify/cli/register/__init__.py +0 -8
  98. hexgate-0.1.2/fortify/cloud/__init__.py +0 -32
  99. hexgate-0.1.2/fortify/security/enforcer.py +0 -102
  100. hexgate-0.1.2/fortify/tools/__init__.py +0 -21
  101. hexgate-0.1.2/fortify/tools/files/__init__.py +0 -9
  102. hexgate-0.1.2/hexgate.egg-info/SOURCES.txt +0 -104
  103. hexgate-0.1.2/hexgate.egg-info/entry_points.txt +0 -2
  104. hexgate-0.1.2/hexgate.egg-info/top_level.txt +0 -1
  105. hexgate-0.1.2/tests/test_bootstrap.py +0 -37
  106. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/adapters/__init__.py +0 -0
  107. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/builtin/researcher/agent.yaml +0 -0
  108. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/builtin/researcher/policy.yaml +0 -0
  109. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/builtin/researcher/system.md +0 -0
  110. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/models.py +0 -0
  111. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/agents/prompts/agent_system.md +0 -0
  112. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/config/__init__.py +0 -0
  113. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/command_policy.py +0 -0
  114. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/sandbox_runtime.py +0 -0
  115. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/runtime/srt.py +0 -0
  116. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/security/errors.py +0 -0
  117. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/tracing/__init__.py +0 -0
  118. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/utils/__init__.py +0 -0
  119. {hexgate-0.1.2/fortify → hexgate-0.2.1/hexgate}/utils/retry.py +0 -0
  120. {hexgate-0.1.2 → hexgate-0.2.1}/hexgate.egg-info/dependency_links.txt +0 -0
  121. {hexgate-0.1.2 → hexgate-0.2.1}/hexgate.egg-info/requires.txt +0 -0
  122. {hexgate-0.1.2 → hexgate-0.2.1}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexgate
3
- Version: 0.1.2
4
- Summary: HexaGate Fortify — authorization infrastructure for AI agents (agent runtime + cloud client).
3
+ Version: 0.2.1
4
+ Summary: HexaGate — authorization infrastructure for AI agents (agent runtime + cloud client).
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
7
7
  Requires-Dist: bashlex>=0.18
@@ -34,9 +34,9 @@ Requires-Dist: pytest>=8.4.1; extra == "dev"
34
34
  Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"
35
35
  Requires-Dist: ruff>=0.12.2; extra == "dev"
36
36
 
37
- # fortify
37
+ # hexgate
38
38
 
39
- `fortify` is a lightweight LangChain-based agent runtime built around:
39
+ `hexgate` is a lightweight LangChain-based agent runtime built around:
40
40
 
41
41
  - `langchain`
42
42
  - `gpt-5.4`
@@ -65,7 +65,7 @@ The runtime preflights `ripgrep` at agent build time and refuses to start when i
65
65
 
66
66
  ## ⚡ Quick Start — Local CLI
67
67
 
68
- If you just want to install `fortify` and try the terminal chat:
68
+ If you just want to install `hexgate` and try the terminal chat:
69
69
 
70
70
  1. Install the package in editable mode.
71
71
  2. Copy the sample environment file.
@@ -75,7 +75,7 @@ If you just want to install `fortify` and try the terminal chat:
75
75
  ```bash
76
76
  python -m pip install -e .
77
77
  cp .env.sample .env
78
- fortify chat --agent example_agent
78
+ hexgate chat --agent example_agent
79
79
  ```
80
80
 
81
81
  Required keys for the example CLI flow:
@@ -84,15 +84,16 @@ Required keys for the example CLI flow:
84
84
  - `LINKUP_API_KEY`
85
85
  - `TAVILY_API_KEY`
86
86
 
87
- Run `fortify --help` to see all subcommands (`chat`, `serve`, `register`), and `fortify <subcommand> --help` for the flags each one accepts.
87
+ Run `hexgate --help` to see all subcommands (`chat`, `serve`, `register`), and `hexgate <subcommand> --help` for the flags each one accepts.
88
88
 
89
89
  Useful next commands:
90
90
 
91
91
  ```bash
92
- fortify chat --list-agents
93
- fortify chat --agent researcher
94
- fortify chat --use examples/file_agents.py --agent workspace_explorer
95
- fortify chat --use examples/research_agents.py --agent update_researcher
92
+ hexgate chat --list-agents
93
+ hexgate chat --agent researcher # by id
94
+ hexgate chat --agent examples.customer_bot:agent # by module:attr (same shape as `hexgate serve`)
95
+ hexgate chat --use examples/file_agents.py --agent workspace_explorer
96
+ hexgate chat --use examples/research_agents.py --agent update_researcher
96
97
  ```
97
98
 
98
99
  The included local agent lives in `examples/example_agent/`, and the CLI can also load:
@@ -101,9 +102,24 @@ The included local agent lives in `examples/example_agent/`, and the CLI can als
101
102
  - code-defined agents registered from `examples/file_agents.py`
102
103
  - code-defined research agents registered from `examples/research_agents.py`
103
104
 
105
+ ## 🧭 Which path do I pick?
106
+
107
+ The two Quick Starts above aren't competing — they answer different questions.
108
+
109
+ **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_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.
110
+
111
+ **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.
112
+
113
+ | Path | Needs platform? | Audit destination | Policy edits visible at | Best for |
114
+ |------|-----------------|-------------------|--------------------------|----------|
115
+ | `hexgate chat --agent ...` | No | Local terminal panel | Edit + restart (hot-reload only when `HEXGATE_LOCAL_POLICY` is set) | Inner loop, policy authoring |
116
+ | `hexgate serve --agent ...` + Playground | Yes | ClickHouse via `/v1/audit/decisions` | Per-turn fetch from dashboard | Team review, demos, integration testing |
117
+
118
+ Both commands accept either a plain agent id (`--agent researcher`) or a uvicorn-style `module.path:attr` spec (`--agent examples.customer_bot:agent`), so the same entry-point string works in both workflows.
119
+
104
120
  ## 🚀 Quick Start — Platform
105
121
 
106
- To run the full Fortify 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:
122
+ To run the full HexaGate 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:
107
123
 
108
124
  ```bash
109
125
  make demo-platform # prints the 3-terminal recipe below
@@ -118,15 +134,15 @@ make dashboard
118
134
 
119
135
  # Terminal 3 — mint a token, then serve your local agent
120
136
  # 1. Open http://localhost:5173/tokens, click "Mint new token", copy the value.
121
- # 2. Add to asianf/.env: FORTIFY_KEY=fty_live_...
137
+ # 2. Add to asianf/.env: HEXGATE_KEY=fty_live_...
122
138
  # 3. Pick the agent's Python entrypoint (module:attr — uvicorn-style)
123
- # and let `fortify serve` take over:
139
+ # and let `hexgate serve` take over:
124
140
  make serve # default — examples.customer_bot:agent
125
141
  # or, for a different agent:
126
- uv run fortify serve my_app.agents:my_agent
142
+ uv run hexgate serve my_app.agents:my_agent
127
143
  ```
128
144
 
129
- On first serve, `fortify serve` auto-registers the agent's manifest on
145
+ On first serve, `hexgate serve` auto-registers the agent's manifest on
130
146
  the platform (the server generates a starter role-aware policy from the
131
147
  tool list). Subsequent serves short-circuit if the manifest hasn't
132
148
  changed. Pass `--no-auto-register` for CI / deliberate-deployment flows.
@@ -171,7 +187,7 @@ curl -X POST localhost:8000/v1/audit/decisions \
171
187
 
172
188
  Integration tests (`pytest -m integration`) round-trip rows through the live ClickHouse — opt-in so the default `make platform-api-test` stays offline-friendly.
173
189
 
174
- The dashboard's `/policies` page lets you edit each agent's policy. `fortify serve` re-fetches at every turn boundary, so your edits take effect on the next chat message without a restart.
190
+ 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.
175
191
 
176
192
  ## ✨ Core Primitives
177
193
 
@@ -183,7 +199,7 @@ The two main primitives are:
183
199
  Use them when you want to define everything directly in Python.
184
200
 
185
201
  ```python
186
- from fortify import agent_tool, create_agent
202
+ from hexgate import agent_tool, create_agent
187
203
 
188
204
 
189
205
  @agent_tool(name="my_lookup")
@@ -207,10 +223,10 @@ Devs pick one of two shapes. Both end up at the same enforcement seam — they d
207
223
  Dev wrote an OpenAI Agents / LangChain / Google ADK / Pydantic AI agent. They wrap it once and they're done:
208
224
 
209
225
  ```python
210
- from fortify.adapters.openai import FortifyRunner # or .langchain.wrap_langchain_agent, .google.FortifyRunner, .pydantic_ai.wrap_pydantic_agent
211
- from fortify.runtime import User
226
+ from hexgate.adapters.openai import HexgateRunner # or .langchain.wrap_langchain_agent, .google.HexgateRunner, .pydantic_ai.wrap_pydantic_agent
227
+ from hexgate.runtime import User
212
228
 
213
- runner = FortifyRunner() # picks up FORTIFY_KEY from env
229
+ runner = HexgateRunner() # picks up HEXGATE_KEY from env
214
230
  await runner.run(
215
231
  my_agent,
216
232
  "refund 30",
@@ -230,9 +246,9 @@ That's it. They get:
230
246
  Dev authored the agent's `agent.yaml` / `policy.yaml` / `system.md` in the dashboard. SDK fetches them:
231
247
 
232
248
  ```python
233
- from fortify import load_fortify_agent, stream_agent, User
249
+ from hexgate import load_hexgate_agent, stream_agent, User
234
250
 
235
- agent, handler = load_fortify_agent("default") # explicit name — the SDK's loader requires it
251
+ agent, handler = load_hexgate_agent("default") # explicit name — the SDK's loader requires it
236
252
 
237
253
  async with User(user_id="alice", role="billing"):
238
254
  async for ev in stream_agent(agent, handler, "refund 30"):
@@ -245,12 +261,12 @@ Same enforcement seam, same `User` scope. The difference is whose system of reco
245
261
 
246
262
  | What dev sets | What changes |
247
263
  |---|---|
248
- | `FORTIFY_KEY=fty_live_<project>_…` | Wakes up the platform path. Without it, adapters / `load_agent` fall back to local / builtin. |
249
- | `FORTIFY_API_URL=http://localhost:8000` *(optional)* | Platform endpoint. Defaults to localhost. |
250
- | `FORTIFY_LOCAL_POLICY=./policy.yaml` *or* `./bundle/` | Dev escape hatch: enforce a policy from disk, hot-reload on save. Wins over the platform's bundle. |
251
- | `FORTIFY_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` *(optional)* | Sign locally-recompiled yaml so `bundle.is_signed` reads True. |
252
- | `FORTIFY_BUNDLE_PUBKEY_PATH=./keys/prod.public` *(optional)* | Verify a pre-built bundle dir against this pubkey on every reload. |
253
- | `FORTIFY_BUNDLE_REQUIRE_SIGNATURE=true` *(optional)* | Strict mode — refuse any unsigned or unverifiable bundle at startup. |
264
+ | `HEXGATE_KEY=fty_live_<project>_…` | Wakes up the platform path. Without it, adapters / `load_agent` fall back to local / builtin. |
265
+ | `HEXGATE_API_URL=http://localhost:8000` *(optional)* | Platform endpoint. Defaults to localhost. |
266
+ | `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. |
267
+ | `HEXGATE_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` *(optional)* | Sign locally-recompiled yaml so `bundle.is_signed` reads True. |
268
+ | `HEXGATE_BUNDLE_PUBKEY_PATH=./keys/prod.public` *(optional)* | Verify a pre-built bundle dir against this pubkey on every reload. |
269
+ | `HEXGATE_BUNDLE_REQUIRE_SIGNATURE=true` *(optional)* | Strict mode — refuse any unsigned or unverifiable bundle at startup. |
254
270
 
255
271
  No config object to instantiate, no `enforce_policy(...)` call to remember on the platform path. The adapter / loader threads it all through.
256
272
 
@@ -266,18 +282,18 @@ Walk through one tool call:
266
282
 
267
283
  `_policy_source` is set automatically by the loader based on env:
268
284
 
269
- - `FORTIFY_LOCAL_POLICY` set → `YamlPolicySource` or `BundleDirPolicySource` (mtime-driven refresh)
270
- - `FORTIFY_KEY` set, no local override → `PlatformPolicySource` (ETag / `304 Not Modified` refresh)
285
+ - `HEXGATE_LOCAL_POLICY` set → `YamlPolicySource` or `BundleDirPolicySource` (mtime-driven refresh)
286
+ - `HEXGATE_KEY` set, no local override → `PlatformPolicySource` (ETag / `304 Not Modified` refresh)
271
287
  - Neither → no source attached; enforcement uses whatever was loaded once
272
288
 
273
- **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 `fortify 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.
289
+ **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.
274
290
 
275
291
  ### Two carve-outs worth knowing
276
292
 
277
293
  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).
278
- 2. **`approval_required` tools.** If the policy uses that mode, dev decides what happens — pass `approval_handler=` (True / False / callable) when wrapping. Default for `fortify serve` is auto-approve; for `fortify chat` it prompts the TTY. Native code gets whatever the dev wires.
294
+ 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.
279
295
 
280
- Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `FORTIFY_KEY` and wrap, or set `FORTIFY_LOCAL_POLICY` and wrap. That's the surface.
296
+ Everything else — fetch, verify, hot-reload, role selection, signature check, decision rendering, tracing — the runtime handles. Set `HEXGATE_KEY` and wrap, or set `HEXGATE_LOCAL_POLICY` and wrap. That's the surface.
281
297
 
282
298
  ## 📦 What You Can Import
283
299
 
@@ -292,7 +308,7 @@ The current curated surface includes:
292
308
  - `stream_agent_raw`
293
309
  - `load_builtin_agent`
294
310
  - `list_builtin_agents`
295
- - `load_fortify_agent`
311
+ - `load_hexgate_agent`
296
312
  - `User` — async context manager for per-request user attenuation (see [User Scope](#-user-scope))
297
313
  - `agent_tool`
298
314
  - `web_search`
@@ -301,7 +317,7 @@ The current curated surface includes:
301
317
  Example:
302
318
 
303
319
  ```python
304
- from fortify import (
320
+ from hexgate import (
305
321
  create_agent,
306
322
  edit_file,
307
323
  enforce_policy,
@@ -312,7 +328,7 @@ from fortify import (
312
328
  agent_tool,
313
329
  load_agent,
314
330
  load_builtin_agent,
315
- load_fortify_agent,
331
+ load_hexgate_agent,
316
332
  register_agent,
317
333
  fetch,
318
334
  web_search,
@@ -322,7 +338,7 @@ from fortify import (
322
338
 
323
339
  ## 🤝 Framework Agent Wrapping
324
340
 
325
- In addition to its native `create_agent(...)` runtime, `fortify` 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:
341
+ 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:
326
342
 
327
343
  1. **Tool-call policy enforcement.** Each tool the agent can invoke is gated by a `PolicyEnforcer` that returns a typed `Decision` (allow / deny / needs-approval) per call. Non-allow outcomes render as a `[policy_denied]` / `[approval_required]` marker the model sees as tool output (or, for pydantic_ai, a `ModelRetry`) rather than aborting the run, so the agent can recover.
328
344
  2. **User-aware observability.** Every run is traced through Langfuse with the active `User`'s identity (user id, session id, role) propagated onto the spans.
@@ -331,7 +347,7 @@ The four integrations differ in shape because the underlying SDKs do:
331
347
 
332
348
  | | OpenAI Agents SDK | LangChain / LangGraph | Google ADK | Pydantic AI |
333
349
  | --- | --- | --- | --- | --- |
334
- | Entry point | `FortifyRunner` (replaces `Runner`) | `wrap_langchain_agent` (returns a proxy) | `FortifyRunner` (replaces `Runner`) | `wrap_pydantic_agent` (returns a proxy) |
350
+ | Entry point | `HexgateRunner` (replaces `Runner`) | `wrap_langchain_agent` (returns a proxy) | `HexgateRunner` (replaces `Runner`) | `wrap_pydantic_agent` (returns a proxy) |
335
351
  | Tool wrapping | Copies each `FunctionTool`, replaces `on_invoke_tool` with a `PolicyEnforcer`-gated version | Mutates each `BaseTool` in place (`install_enforcer_on_tool`), replaces `func`/`coroutine` with enforcer-gated versions, sets `handle_tool_error=True` | Copies each `BaseTool` (normalizing bare callables to `FunctionTool`), replaces `run_async` with a gated version | Copies each `Tool` and overrides `function_schema.call` with a gated version |
336
352
  | Denial behavior | Returns `decision.as_error_message()` as the tool output (`[policy_denied]` / `[approval_required]` markered string) | Returns `{"ok": False, "error": decision.as_error_payload()}` so LangChain emits the structured dict as the tool result | Returns `decision.as_error_message()` as the tool output | Raises `ModelRetry(decision.as_error_message())`; pydantic_ai surfaces it back to the model as a tool-result message |
337
353
  | Tracing | `OpenAIAgentsInstrumentor` + `propagate_attributes` | Langfuse `CallbackHandler` injected into each call's `RunnableConfig` + `propagate_attributes` | `GoogleADKInstrumentor` + `propagate_attributes` | `Agent.instrument_all()` + `propagate_attributes` |
@@ -339,19 +355,19 @@ The four integrations differ in shape because the underlying SDKs do:
339
355
 
340
356
  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.
341
357
 
342
- All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `FORTIFY_KEY` environment variable.
358
+ All adapters resolve the API key the same way: from the explicit `api_key=` argument, falling back to the `HEXGATE_KEY` environment variable.
343
359
 
344
- ### OpenAI Agents SDK — `FortifyRunner`
360
+ ### OpenAI Agents SDK — `HexgateRunner`
345
361
 
346
- `FortifyRunner` is a drop-in replacement for `agents.Runner`. It wraps the agent's tools with a `PolicyEnforcer` at construction time and opens a `User` scope around each `Runner.run` / `run_sync` / `run_streamed` call so role resolution happens at call time.
362
+ `HexgateRunner` is a drop-in replacement for `agents.Runner`. It wraps the agent's tools with a `PolicyEnforcer` at construction time and opens a `User` scope around each `Runner.run` / `run_sync` / `run_streamed` call so role resolution happens at call time.
347
363
 
348
364
  ```python
349
365
  import asyncio
350
366
  from agents import Agent, function_tool
351
367
  from dotenv import load_dotenv
352
368
 
353
- from fortify.runtime import User
354
- from fortify.adapters.openai import FortifyRunner
369
+ from hexgate.runtime import User
370
+ from hexgate.adapters.openai import HexgateRunner
355
371
 
356
372
 
357
373
  @function_tool
@@ -369,7 +385,7 @@ async def main():
369
385
  model="gpt-4o-mini",
370
386
  )
371
387
 
372
- runner = FortifyRunner() # picks up FORTIFY_KEY from env
388
+ runner = HexgateRunner() # picks up HEXGATE_KEY from env
373
389
  result = await runner.run(
374
390
  agent,
375
391
  "What's the weather in Cherbourg?",
@@ -384,7 +400,7 @@ if __name__ == "__main__":
384
400
 
385
401
  What happens under the hood:
386
402
 
387
- - `FortifyRunner.run` calls `wrap_openai_agent`, which builds a `PolicySet` for `(api_key, agent.name, tool_names)`, constructs one `PolicyEnforcer`, and returns a `dataclasses.replace`'d copy of the agent with policy-gated tool copies — your original `agent` is untouched.
403
+ - `HexgateRunner.run` calls `wrap_openai_agent`, which builds a `PolicySet` for `(api_key, agent.name, tool_names)`, constructs one `PolicyEnforcer`, and returns a `dataclasses.replace`'d copy of the agent with policy-gated tool copies — your original `agent` is untouched.
388
404
  - The runner opens an `async with user:` scope around the underlying `Runner.run*` call. When the model calls a tool, the guard asks `enforcer.decide(...)` for a `Decision`. On non-allow, it returns `decision.as_error_message()` — a `[policy_denied]` or `[approval_required]` markered string the model can interpret and recover from.
389
405
  - The run executes inside `propagate_attributes(user_id=..., session_id=..., metadata={"user_role": ...})`, so Langfuse spans carry the caller identity.
390
406
 
@@ -392,7 +408,7 @@ What happens under the hood:
392
408
 
393
409
  ### LangChain / LangGraph — `wrap_langchain_agent`
394
410
 
395
- `wrap_langchain_agent` builds a `PolicyEnforcer` once and installs it on each tool in place (`install_enforcer_on_tool`) so the same instances inside the compiled graph become policy-gated. It returns a `FortifyLangchainAgent` proxy that opens a `User` scope and injects a Langfuse callback into every `invoke` / `ainvoke` / `stream` / `astream` / `astream_events` call. The `user` is supplied **per call**, so a single wrapped agent can serve many users concurrently — role resolution happens at call time from the contextvar.
411
+ `wrap_langchain_agent` builds a `PolicyEnforcer` once and installs it on each tool in place (`install_enforcer_on_tool`) so the same instances inside the compiled graph become policy-gated. It returns a `HexgateLangchainAgent` proxy that opens a `User` scope and injects a Langfuse callback into every `invoke` / `ainvoke` / `stream` / `astream` / `astream_events` call. The `user` is supplied **per call**, so a single wrapped agent can serve many users concurrently — role resolution happens at call time from the contextvar.
396
412
 
397
413
  ```python
398
414
  import asyncio
@@ -401,8 +417,8 @@ from langchain_core.tools import tool
401
417
  from langchain_openai import ChatOpenAI
402
418
  from langgraph.prebuilt import create_react_agent
403
419
 
404
- from fortify.runtime import User
405
- from fortify.adapters.langchain import wrap_langchain_agent
420
+ from hexgate.runtime import User
421
+ from hexgate.adapters.langchain import wrap_langchain_agent
406
422
 
407
423
 
408
424
  @tool
@@ -429,7 +445,7 @@ async def main():
429
445
  agent = wrap_langchain_agent(
430
446
  agent=graph,
431
447
  tools=TOOLS, # same list passed to create_react_agent — wrapped in place
432
- api_key="sk-...", # or rely on FORTIFY_KEY
448
+ api_key="sk-...", # or rely on HEXGATE_KEY
433
449
  )
434
450
 
435
451
  result = await agent.ainvoke(
@@ -446,13 +462,13 @@ if __name__ == "__main__":
446
462
  What happens under the hood:
447
463
 
448
464
  - `wrap_langchain_agent` builds a `PolicySet` for the agent, constructs one `PolicyEnforcer(policy_set, agent_name=…)`, and calls `install_enforcer_on_tools(tools, enforcer=…)` to mutate each tool's `func` and `coroutine` with enforcer-gated closures. `handle_tool_error` is forced to `True`. Installation is idempotent — re-installing rebinds the captured originals to the new enforcer without stacking gates.
449
- - Each invocation method on `FortifyLangchainAgent` takes `user=` and opens an `async with user:` (or `user.sync_scope()` for sync) around the delegated `CompiledStateGraph` call. The active `User` is pushed onto a contextvar; the guards read it at tool-call time to resolve the matching role's policy.
465
+ - Each invocation method on `HexgateLangchainAgent` takes `user=` and opens an `async with user:` (or `user.sync_scope()` for sync) around the delegated `CompiledStateGraph` call. The active `User` is pushed onto a contextvar; the guards read it at tool-call time to resolve the matching role's policy.
450
466
  - A non-allow `Decision` is rendered as `{"ok": False, "error": decision.as_error_payload()}` so the LangChain runtime surfaces the structured dict as the tool result instead of raising.
451
467
  - The wrapper also enters `propagate_attributes(user_id=..., session_id=..., metadata={"user_role": ...})` and merges a Langfuse `CallbackHandler` into the `RunnableConfig.callbacks` for the duration of the call. Anything not explicitly proxied falls through via `__getattr__`.
452
468
 
453
- ### Google ADK — `FortifyRunner`
469
+ ### Google ADK — `HexgateRunner`
454
470
 
455
- The Google ADK wrapper exposes its own `FortifyRunner`. It's constructed up front with the agent, app name, and session service (mirroring the ADK `Runner` constructor) — the underlying ADK `Runner` is built once and reused since role resolution happens at call time. `run` / `run_async` then yield ADK events.
471
+ The Google ADK wrapper exposes its own `HexgateRunner`. It's constructed up front with the agent, app name, and session service (mirroring the ADK `Runner` constructor) — the underlying ADK `Runner` is built once and reused since role resolution happens at call time. `run` / `run_async` then yield ADK events.
456
472
 
457
473
  ```python
458
474
  import asyncio
@@ -464,8 +480,8 @@ from google.adk.models.lite_llm import LiteLlm
464
480
  from google.adk.sessions import InMemorySessionService
465
481
  from google.genai import types
466
482
 
467
- from fortify.runtime import User
468
- from fortify.adapters.google import FortifyRunner
483
+ from hexgate.runtime import User
484
+ from hexgate.adapters.google import HexgateRunner
469
485
 
470
486
 
471
487
  def get_weather(city: str) -> str:
@@ -501,11 +517,11 @@ async def main():
501
517
  session_id=user.session_id,
502
518
  )
503
519
 
504
- runner = FortifyRunner(
520
+ runner = HexgateRunner(
505
521
  agent=agent,
506
522
  app_name="google_runner_example",
507
523
  session_service=session_service,
508
- ) # picks up FORTIFY_KEY from env
524
+ ) # picks up HEXGATE_KEY from env
509
525
 
510
526
  user_msg = types.Content(
511
527
  role="user", parts=[types.Part(text="What is the weather in New Delhi?")]
@@ -522,22 +538,22 @@ if __name__ == "__main__":
522
538
 
523
539
  What happens under the hood:
524
540
 
525
- - At construction, `FortifyRunner` calls `wrap_google_agent`, which builds a `PolicySet`, constructs one `PolicyEnforcer`, and returns `agent.model_copy(update={"tools": guarded_tools})` — your original `agent` is untouched.
541
+ - At construction, `HexgateRunner` calls `wrap_google_agent`, which builds a `PolicySet`, constructs one `PolicyEnforcer`, and returns `agent.model_copy(update={"tools": guarded_tools})` — your original `agent` is untouched.
526
542
  - Each tool is normalized first: bare callables in `agent.tools` are wrapped into `FunctionTool` (matching what ADK does internally) so the guard has a stable `BaseTool` surface. Each tool is then `copy.copy`'d and its `run_async` replaced with an enforcer-gated version.
527
543
  - Each `run` / `run_async` call opens a `User` scope (`user.sync_scope()` / `async with user:`) and dispatches to the cached underlying `Runner`. On non-allow, the guard returns `decision.as_error_message()` — a `[policy_denied]` or `[approval_required]` markered string — so the ADK runtime forwards it to the model as the tool output instead of aborting the run.
528
544
  - Observability is set up lazily on each call: `GoogleADKInstrumentor().instrument()` plus `nest_asyncio.apply()` (ADK's runner spins its own loop), and the run executes inside `propagate_attributes(user_id=..., session_id=..., metadata={"user_role": ...}, tags=["google.runner.run.<agent_name>"])` so Langfuse spans carry the caller identity.
529
545
 
530
546
  ### Pydantic AI — `wrap_pydantic_agent`
531
547
 
532
- `wrap_pydantic_agent` returns a `FortifyPydanticAgent` proxy backed by a clone of the original agent whose tools are gated by a freshly built `PolicyEnforcer`. Tools registered via the `Agent(...)` constructor or via `@agent.tool` / `@agent.tool_plain` are all picked up. The `user` is supplied **per call**, so a single wrapped agent can serve many users concurrently — role resolution happens at call time from the contextvar.
548
+ `wrap_pydantic_agent` returns a `HexgatePydanticAgent` proxy backed by a clone of the original agent whose tools are gated by a freshly built `PolicyEnforcer`. Tools registered via the `Agent(...)` constructor or via `@agent.tool` / `@agent.tool_plain` are all picked up. The `user` is supplied **per call**, so a single wrapped agent can serve many users concurrently — role resolution happens at call time from the contextvar.
533
549
 
534
550
  ```python
535
551
  import asyncio
536
552
  from dotenv import load_dotenv
537
553
  from pydantic_ai import Agent
538
554
 
539
- from fortify.runtime import User
540
- from fortify.adapters.pydantic_ai import wrap_pydantic_agent
555
+ from hexgate.runtime import User
556
+ from hexgate.adapters.pydantic_ai import wrap_pydantic_agent
541
557
 
542
558
 
543
559
  async def main():
@@ -557,7 +573,7 @@ async def main():
557
573
 
558
574
  agent = wrap_pydantic_agent(
559
575
  agent=agent,
560
- api_key="sk-...", # or rely on FORTIFY_KEY
576
+ api_key="sk-...", # or rely on HEXGATE_KEY
561
577
  )
562
578
 
563
579
  result = await agent.run(
@@ -578,7 +594,7 @@ if __name__ == "__main__":
578
594
  What happens under the hood:
579
595
 
580
596
  - `wrap_pydantic_agent` builds a `PolicySet`, constructs one `PolicyEnforcer`, reads tools off the agent's internal `_function_toolset`, copies each tool with an enforcer-gated `function_schema.call`, and returns a shallow-copied agent whose toolset holds those gated copies — your original `agent` is untouched, so it can be reused or wrapped again independently.
581
- - Each invocation method on `FortifyPydanticAgent` (`run` / `run_sync` / `run_stream` / `iter`) takes `user=` and opens a `User` scope around the delegated `Agent` call. The contextvar is per-task, so concurrent `run` calls for different users do not see each other's policies.
597
+ - Each invocation method on `HexgatePydanticAgent` (`run` / `run_sync` / `run_stream` / `iter`) takes `user=` and opens a `User` scope around the delegated `Agent` call. The contextvar is per-task, so concurrent `run` calls for different users do not see each other's policies.
582
598
  - A non-allow `Decision` raises `ModelRetry(decision.as_error_message())`; pydantic_ai surfaces it back to the model as a tool-result message — `[policy_denied]` / `[approval_required]` markers in the same shape as the OpenAI/Google adapters — instead of aborting the run.
583
599
  - Identity propagation uses `propagate_attributes(...)` so Langfuse spans carry the caller identity. Global tracing is enabled via `Agent.instrument_all()` on construction.
584
600
 
@@ -586,9 +602,9 @@ What happens under the hood:
586
602
 
587
603
  Working scripts in `examples/`:
588
604
 
589
- - `examples/customer_bot.py` — canonical Fortify path: `create_agent(...)` + the dashboard register/serve loop end-to-end.
590
- - `examples/openai_demo.py` — `FortifyRunner` (OpenAI Agents SDK) end-to-end.
591
- - `examples/google_demo.py` — `FortifyRunner` (Google ADK) end-to-end with `InMemorySessionService`.
605
+ - `examples/customer_bot.py` — canonical HexaGate path: `create_agent(...)` + the dashboard register/serve loop end-to-end.
606
+ - `examples/openai_demo.py` — `HexgateRunner` (OpenAI Agents SDK) end-to-end.
607
+ - `examples/google_demo.py` — `HexgateRunner` (Google ADK) end-to-end with `InMemorySessionService`.
592
608
  - `examples/pydantic_ai_demo.py` — `wrap_pydantic_agent` (Pydantic AI) end-to-end.
593
609
 
594
610
  > **Note on naming.** These demo files end in `_demo.py` so their filenames don't shadow the installed packages they import (`agents`, `google`, `langchain`, `openai`, `pydantic_ai`). Without the suffix, running any script inside `examples/` would put the directory on `sys.path[0]` and Python would import the demo files instead of the real packages.
@@ -615,14 +631,14 @@ It demonstrates:
615
631
  For the CLI, you can import that script and then pick one of its registered agents:
616
632
 
617
633
  ```bash
618
- fortify chat --use examples/file_agents.py --agent workspace_explorer
619
- fortify chat --use examples/file_agents.py --agent repo_editor
620
- fortify chat --use examples/research_agents.py --agent update_researcher
634
+ hexgate chat --use examples/file_agents.py --agent workspace_explorer
635
+ hexgate chat --use examples/file_agents.py --agent repo_editor
636
+ hexgate chat --use examples/research_agents.py --agent update_researcher
621
637
  ```
622
638
 
623
639
  ## 🗂️ Builtin And Local Agents
624
640
 
625
- The package now ships with a small `fortify.builtin_agents` directory for official starter agents.
641
+ The package now ships with a small `hexgate.builtin_agents` directory for official starter agents.
626
642
 
627
643
  Current builtin agents:
628
644
 
@@ -631,7 +647,7 @@ Current builtin agents:
631
647
  Example:
632
648
 
633
649
  ```python
634
- from fortify import load_builtin_agent
650
+ from hexgate import load_builtin_agent
635
651
 
636
652
  agent, handler = load_builtin_agent("researcher")
637
653
  ```
@@ -645,7 +661,7 @@ The CLI also discovers local agents from:
645
661
  This repo ships a demo agent at `examples/example_agent/`, so from the project root you can simply run:
646
662
 
647
663
  ```bash
648
- fortify chat --agent example_agent
664
+ hexgate chat --agent example_agent
649
665
  ```
650
666
 
651
667
  ## 🔐 Policy Shape
@@ -685,7 +701,7 @@ Every tool call routes through a `PolicyEnforcer` that returns `allow` / `deny`
685
701
  `create_agent(...)` stays close to LangChain. Policy enforcement is applied after agent creation:
686
702
 
687
703
  ```python
688
- from fortify import AgentPolicy, create_agent, enforce_policy, fetch, web_search
704
+ from hexgate import AgentPolicy, create_agent, enforce_policy, fetch, web_search
689
705
 
690
706
  policy = AgentPolicy.model_validate(
691
707
  {
@@ -721,7 +737,7 @@ That means the same agent code can stay simple in development, while deployment
721
737
 
722
738
  ## 🧩 Policy Bundles — Compile, Sign, Enforce (WASM)
723
739
 
724
- Fortify has **two policy enforcement engines** that return identical decisions (there's a parity test suite that proves it):
740
+ HexaGate has **two policy enforcement engines** that return identical decisions (there's a parity test suite that proves it):
725
741
 
726
742
  - **pydantic** (default) — evaluates constraints in-process. Zero setup; this is what every example above uses.
727
743
  - **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.
@@ -737,24 +753,24 @@ brew install opa # macOS
737
753
  # or see https://www.openpolicyagent.org/docs/latest/#running-opa
738
754
  ```
739
755
 
740
- Without `opa` on `PATH`, `fortify policy build --no-wasm` still emits the yaml + rego (no `.wasm`), and the pydantic engine keeps working.
756
+ Without `opa` on `PATH`, `hexgate policy build --no-wasm` still emits the yaml + rego (no `.wasm`), and the pydantic engine keeps working.
741
757
 
742
- ### The `fortify policy` CLI
758
+ ### The `hexgate policy` CLI
743
759
 
744
760
  ```bash
745
761
  # Validate a policy.yaml without the network — parse + check every constraint
746
- fortify policy validate policy.yaml
762
+ hexgate policy validate policy.yaml
747
763
 
748
764
  # See the Rego your YAML compiles to (stdout)
749
- fortify policy show-rego policy.yaml
765
+ hexgate policy show-rego policy.yaml
750
766
 
751
767
  # Dry-run a single decision. --engine wasm compiles + evaluates in wasmtime
752
768
  # (matching production); the default pydantic engine needs no opa.
753
- fortify policy test policy.yaml --role billing --tool refund_order \
769
+ hexgate policy test policy.yaml --role billing --tool refund_order \
754
770
  --args '{"amount": 200, "currency": "USD"}' --engine wasm
755
771
 
756
772
  # Compile a bundle: writes {stem}.yaml + .rego + .wasm + .bundle.json
757
- fortify policy build policy.yaml --out ./bundle
773
+ hexgate policy build policy.yaml --out ./bundle
758
774
  ```
759
775
 
760
776
  On a denied decision, `test` prints the reason; the wasm engine additionally lists each violated constraint string verbatim:
@@ -768,7 +784,7 @@ On a denied decision, `test` prints the reason; the wasm engine additionally lis
768
784
 
769
785
  ### What's in a bundle
770
786
 
771
- `fortify policy build` produces a directory:
787
+ `hexgate policy build` produces a directory:
772
788
 
773
789
  | File | Contents |
774
790
  |---|---|
@@ -780,29 +796,29 @@ On a denied decision, `test` prints the reason; the wasm engine additionally lis
780
796
 
781
797
  The manifest's hashes authenticate the files; the signature authenticates the manifest. Verifying both proves the whole bundle came from the trusted signer, untampered.
782
798
 
783
- ### Local enforcement — `FORTIFY_LOCAL_POLICY`
799
+ ### Local enforcement — `HEXGATE_LOCAL_POLICY`
784
800
 
785
801
  Point an agent at a local source and every tool call routes through the WASM engine instead of pydantic — no platform needed. Two shapes are accepted, and both **hot-reload on save** (no restart, no manual rebuild between turns):
786
802
 
787
- | `FORTIFY_LOCAL_POLICY=…` | What happens | When to use |
803
+ | `HEXGATE_LOCAL_POLICY=…` | What happens | When to use |
788
804
  |---|---|---|
789
- | **`./bundle/`** (output of `fortify policy build`) | Stat the bundle manifest each turn; reload if its mtime changed. | Production-shaped local testing — exercises the exact signed-bundle path. |
805
+ | **`./bundle/`** (output of `hexgate policy build`) | Stat the bundle manifest each turn; reload if its mtime changed. | Production-shaped local testing — exercises the exact signed-bundle path. |
790
806
  | **`./policy.yaml`** | Stat the yaml each turn; recompile via `opa` when its mtime changed. | The tight dev loop — edit yaml, save, ask again. No build step. |
791
807
 
792
808
  ```bash
793
809
  # Pre-built bundle dir — rebuild it mid-session, next chat picks it up
794
- fortify policy build policy.yaml --out ./bundle
795
- FORTIFY_LOCAL_POLICY=./bundle fortify chat --agent researcher
796
- # [fortify] FORTIFY_LOCAL_POLICY active (bundle-dir): ./bundle (wasm_hash=7e6d1f8b..., unsigned)
810
+ hexgate policy build policy.yaml --out ./bundle
811
+ HEXGATE_LOCAL_POLICY=./bundle hexgate chat --agent researcher
812
+ # [hexgate] HEXGATE_LOCAL_POLICY active (bundle-dir): ./bundle (wasm_hash=7e6d1f8b..., unsigned)
797
813
 
798
814
  # Raw yaml — edit policy.yaml in your editor, save, next chat sees the new policy
799
- FORTIFY_LOCAL_POLICY=./policy.yaml fortify chat --agent researcher
800
- # [fortify] FORTIFY_LOCAL_POLICY active (yaml): ./policy.yaml (wasm_hash=ab12..., unsigned)
815
+ HEXGATE_LOCAL_POLICY=./policy.yaml hexgate chat --agent researcher
816
+ # [hexgate] HEXGATE_LOCAL_POLICY active (yaml): ./policy.yaml (wasm_hash=ab12..., unsigned)
801
817
  ```
802
818
 
803
- The bundle's integrity (files match the manifest) is verified on every reload — a stale or corrupt bundle fails immediately, not at the first tool call. Yaml sources default to **unsigned**: set `FORTIFY_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` to sign each recompile with your `fortify policy keygen` key, so downstream gates that check `bundle.is_signed` see what they expect.
819
+ The bundle's integrity (files match the manifest) is verified on every reload — a stale or corrupt bundle fails immediately, not at the first tool call. Yaml sources default to **unsigned**: set `HEXGATE_BUNDLE_SIGN_KEY_PATH=./keys/dev.private` to sign each recompile with your `hexgate policy keygen` key, so downstream gates that check `bundle.is_signed` see what they expect.
804
820
 
805
- > **Same refresh seam as the platform.** Under the hood both sources implement `PolicySource.fetch()`; the agent runtime calls it at the top of every turn and only swaps the active policy when the returned bundle is a new instance. Unchanged → identity match → no work. That's the same hot-reload path `fortify serve` uses for platform-edited YAML.
821
+ > **Same refresh seam as the platform.** Under the hood both sources implement `PolicySource.fetch()`; the agent runtime calls it at the top of every turn and only swaps the active policy when the returned bundle is a new instance. Unchanged → identity match → no work. That's the same hot-reload path `hexgate serve` uses for platform-edited YAML.
806
822
 
807
823
  ### Signing & verification
808
824
 
@@ -811,22 +827,22 @@ Production bundles are signed so the runtime can prove a bundle is genuine befor
811
827
  Generate a keypair and sign a bundle locally:
812
828
 
813
829
  ```bash
814
- fortify policy keygen --out ./keys/dev # → dev.private (0600) + dev.public
815
- fortify policy build policy.yaml --out ./bundle --sign-key ./keys/dev.private
830
+ hexgate policy keygen --out ./keys/dev # → dev.private (0600) + dev.public
831
+ hexgate policy build policy.yaml --out ./bundle --sign-key ./keys/dev.private
816
832
  # → ./bundle/policy.bundle.json.sig
817
833
  ```
818
834
 
819
835
  At runtime, point the verifier at the public key:
820
836
 
821
837
  ```bash
822
- FORTIFY_LOCAL_POLICY=./bundle \
823
- FORTIFY_BUNDLE_PUBKEY_PATH=./keys/dev.public \
824
- FORTIFY_BUNDLE_REQUIRE_SIGNATURE=true \
825
- fortify chat --agent researcher
826
- # [fortify] FORTIFY_LOCAL_POLICY active (bundle-dir): ./bundle (wasm_hash=..., signed)
838
+ HEXGATE_LOCAL_POLICY=./bundle \
839
+ HEXGATE_BUNDLE_PUBKEY_PATH=./keys/dev.public \
840
+ HEXGATE_BUNDLE_REQUIRE_SIGNATURE=true \
841
+ hexgate chat --agent researcher
842
+ # [hexgate] HEXGATE_LOCAL_POLICY active (bundle-dir): ./bundle (wasm_hash=..., signed)
827
843
  ```
828
844
 
829
- `FORTIFY_BUNDLE_REQUIRE_SIGNATURE` controls strictness — warn-by-default keeps local dev frictionless; opt into refusal for CI/prod:
845
+ `HEXGATE_BUNDLE_REQUIRE_SIGNATURE` controls strictness — warn-by-default keeps local dev frictionless; opt into refusal for CI/prod:
830
846
 
831
847
  | Bundle | `PUBKEY_PATH` set | `REQUIRE_SIGNATURE` | Outcome |
832
848
  |---|---|---|---|
@@ -858,7 +874,7 @@ The `action` dict carries `{"tool_name", "arguments", "agent_name"}`; `context`
858
874
  Example:
859
875
 
860
876
  ```python
861
- from fortify import (
877
+ from hexgate import (
862
878
  AgentPolicy,
863
879
  create_agent,
864
880
  edit_file,
@@ -913,7 +929,7 @@ Supported on **macOS and Linux only** (Windows is unsupported). If `srt` is not
913
929
  Tune the boundary through `LocalWorkspace`:
914
930
 
915
931
  ```python
916
- from fortify.runtime import LocalWorkspace
932
+ from hexgate.runtime import LocalWorkspace
917
933
 
918
934
  workspace = LocalWorkspace(
919
935
  root_dir="./project",
@@ -988,11 +1004,11 @@ Policy-bundle enforcement (see [Policy Bundles](#-policy-bundles--compile-sign-e
988
1004
 
989
1005
  | Env var | Purpose |
990
1006
  |---|---|
991
- | `FORTIFY_LOCAL_POLICY` | Path to a bundle directory **or** a `policy.yaml`; routes enforcement through the WASM engine and hot-reloads on save |
992
- | `FORTIFY_BUNDLE_PUBKEY_PATH` | base64url Ed25519 public key used to verify a bundle's signature |
993
- | `FORTIFY_BUNDLE_SIGN_KEY_PATH` | base64url Ed25519 private key used to sign locally-compiled yaml sources (so `bundle.is_signed` is True) |
994
- | `FORTIFY_BUNDLE_REQUIRE_SIGNATURE` | `true` to refuse unsigned or unverifiable bundles (default: warn only) |
995
- | `FORTIFY_OPA_BIN` | Override the `opa` binary location (default: search `PATH`) |
1007
+ | `HEXGATE_LOCAL_POLICY` | Path to a bundle directory **or** a `policy.yaml`; routes enforcement through the WASM engine and hot-reloads on save |
1008
+ | `HEXGATE_BUNDLE_PUBKEY_PATH` | base64url Ed25519 public key used to verify a bundle's signature |
1009
+ | `HEXGATE_BUNDLE_SIGN_KEY_PATH` | base64url Ed25519 private key used to sign locally-compiled yaml sources (so `bundle.is_signed` is True) |
1010
+ | `HEXGATE_BUNDLE_REQUIRE_SIGNATURE` | `true` to refuse unsigned or unverifiable bundles (default: warn only) |
1011
+ | `HEXGATE_OPA_BIN` | Override the `opa` binary location (default: search `PATH`) |
996
1012
 
997
1013
  ## 🧪 Tests & Dev Tooling
998
1014
 
@@ -1018,11 +1034,11 @@ Targets at a glance:
1018
1034
  | **M2 policy demo** | |
1019
1035
  | `policy-build` | Compile the example policy.yaml to a bundle |
1020
1036
  | `policy-test-wasm` | Smoke a WASM-engine decision |
1021
- | `demo-override` | Build a deny bundle + chat with `FORTIFY_LOCAL_POLICY` |
1037
+ | `demo-override` | Build a deny bundle + chat with `HEXGATE_LOCAL_POLICY` |
1022
1038
  | **Platform demo** (multi-terminal — see `make demo-platform`) | |
1023
1039
  | `platform-api` / `platform-api-install` / `platform-api-test` | FastAPI control plane in `platform/api/` |
1024
1040
  | `dashboard` / `dashboard-install` | Vite + React app in `platform/dashboard/` |
1025
- | `serve` | `fortify serve` — bridge this SDK to the platform |
1041
+ | `serve` | `hexgate serve` — bridge this SDK to the platform |
1026
1042
  | `demo-platform` | Print the 3-terminal recipe |
1027
1043
  | **Misc** | |
1028
1044
  | `build` / `clean` | Package + tidy |
@@ -1060,40 +1076,40 @@ python examples/demo.py
1060
1076
  Run the inline chat CLI with a local or builtin YAML agent:
1061
1077
 
1062
1078
  ```bash
1063
- fortify chat --agent example_agent
1079
+ hexgate chat --agent example_agent
1064
1080
  ```
1065
1081
 
1066
1082
  Run the CLI with code-defined agents from a Python script:
1067
1083
 
1068
1084
  ```bash
1069
- fortify chat --use examples/file_agents.py --agent workspace_explorer
1070
- fortify chat --use examples/file_agents.py --agent repo_editor
1071
- fortify chat --use examples/research_agents.py --agent update_researcher
1072
- fortify chat --use examples/research_agents.py --agent update_researcher --approval-mode ask
1085
+ 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
+ hexgate chat --use examples/research_agents.py --agent update_researcher --approval-mode ask
1073
1089
  ```
1074
1090
 
1075
1091
  List what the CLI can currently resolve:
1076
1092
 
1077
1093
  ```bash
1078
- fortify chat --list-agents
1094
+ hexgate chat --list-agents
1079
1095
  ```
1080
1096
 
1081
- ### `fortify register` — push a manifest to the platform
1097
+ ### `hexgate register` — push a manifest to the platform
1082
1098
 
1083
- Register a code-defined agent's manifest with the Fortify platform. `--agent`
1099
+ Register a code-defined agent's manifest with the HexaGate platform. `--agent`
1084
1100
  takes a Python import path of the form `module.path:attribute`, the same shape
1085
1101
  as ASGI/WSGI entrypoints. The CLI imports the module, grabs the agent object,
1086
- and POSTs its manifest to `${FORTIFY_API_URL}/v1/agents` using
1087
- `${FORTIFY_KEY}` as the bearer token:
1102
+ and POSTs its manifest to `${HEXGATE_API_URL}/v1/agents` using
1103
+ `${HEXGATE_KEY}` as the bearer token:
1088
1104
 
1089
1105
  ```bash
1090
- fortify register --agent examples.customer_bot:agent
1091
- fortify register --agent my_app.agents:my_agent --description "Customer support bot"
1106
+ hexgate register --agent examples.customer_bot:agent
1107
+ hexgate register --agent my_app.agents:my_agent --description "Customer support bot"
1092
1108
  ```
1093
1109
 
1094
1110
  On first register, the platform auto-generates a starter role-aware
1095
1111
  policy from the manifest's tool list (`read_only` mixin + `default` +
1096
- `member` + `admin`) and signs a WASM bundle so `fortify serve` runs
1112
+ `member` + `admin`) and signs a WASM bundle so `hexgate serve` runs
1097
1113
  against real enforcement from the first request. Edit the policy in
1098
1114
  the dashboard's `/policies` page; subsequent re-registers preserve
1099
1115
  those edits — only the manifest snapshot grows.
@@ -1105,25 +1121,25 @@ you can pass each of those pieces explicitly. Only `--tools` is required;
1105
1121
  fields on the manifest so the dashboard can show them:
1106
1122
 
1107
1123
  ```bash
1108
- fortify register \
1124
+ hexgate register \
1109
1125
  --agent my_app.agents:graph \
1110
1126
  --tools my_app.tools:my_tools \
1111
1127
  --model gpt-4o-mini \
1112
1128
  --system-prompt prompts/support.md
1113
1129
  ```
1114
1130
 
1115
- For everyone else — agents built with `fortify.create_agent(...)`, OpenAI
1131
+ For everyone else — agents built with `hexgate.create_agent(...)`, OpenAI
1116
1132
  Agents, Pydantic AI, Google ADK — the manifest reads tools, model, and
1117
1133
  system prompt directly off the object. No flags needed.
1118
1134
 
1119
1135
  `--system-prompt` accepts either a literal string or a path to a `.md` /
1120
1136
  `.txt` / `.jinja` file (read as text at register time).
1121
1137
 
1122
- Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph, Fortify agents.
1138
+ Supported frameworks: OpenAI Agents SDK, Google ADK, Pydantic AI, LangChain/LangGraph, HexaGate agents.
1123
1139
 
1124
- ### `fortify serve` — bridge a local agent to the platform's relay
1140
+ ### `hexgate serve` — bridge a local agent to the platform's relay
1125
1141
 
1126
- `fortify serve` takes the **same** `module:attr` spec as `fortify register`.
1142
+ `hexgate serve` takes the **same** `module:attr` spec as `hexgate register`.
1127
1143
  The CLI imports the agent, derives the manifest in one call, auto-registers
1128
1144
  on the platform (idempotent — content-hash short-circuits no-ops), fetches
1129
1145
  the operator's policy from the cloud, and opens the WebSocket relay so the
@@ -1131,13 +1147,13 @@ dashboard's Playground can drive it. Policy edits in `/policies` take
1131
1147
  effect at the next chat-turn boundary.
1132
1148
 
1133
1149
  ```bash
1134
- fortify serve examples.customer_bot:agent
1150
+ hexgate serve examples.customer_bot:agent
1135
1151
 
1136
1152
  # CI / deliberate-deploy: error if not pre-registered
1137
- fortify serve examples.customer_bot:agent --no-auto-register
1153
+ hexgate serve examples.customer_bot:agent --no-auto-register
1138
1154
  ```
1139
1155
 
1140
- There is **no** `FORTIFY_AGENT_NAME` env var anymore — the name lives in
1156
+ There is **no** `HEXGATE_AGENT_NAME` env var anymore — the name lives in
1141
1157
  the agent's `.name` attribute (or the `name=` kwarg you passed to
1142
1158
  `create_react_agent` / `create_agent`). The platform is the source of
1143
1159
  truth for policy; your Python file is the source of truth for code.
@@ -1149,24 +1165,24 @@ inspect it, persist it elsewhere, diff it across versions, or wire it
1149
1165
  into a custom registration flow — call `create_manifest` directly:
1150
1166
 
1151
1167
  ```python
1152
- from fortify import create_manifest
1168
+ from hexgate import create_manifest
1153
1169
 
1154
1170
  manifest = create_manifest(agent, description="Customer support bot")
1155
1171
  print(manifest.model_dump())
1156
1172
  ```
1157
1173
 
1158
1174
  `create_manifest` dispatches on the framework of `agent`. The supported
1159
- types are the same set `fortify register` accepts: Fortify, OpenAI Agents
1175
+ types are the same set `hexgate register` accepts: HexaGate, OpenAI Agents
1160
1176
  SDK, Google ADK, Pydantic AI, and LangChain/LangGraph compiled graphs.
1161
1177
  For LangGraph you must pass `tools=` explicitly, and may pass `model=` /
1162
1178
  `system_prompt=`, since compiled graphs don't expose those fields after
1163
1179
  compilation.
1164
1180
 
1165
1181
  The return value is an `AgentManifest` (a Pydantic model, also re-exported
1166
- from `fortify` for type annotations) — the same schema the platform
1182
+ from `hexgate` for type annotations) — the same schema the platform
1167
1183
  stores and the dashboard renders.
1168
1184
 
1169
- ## 🌐 Fortify Platform
1185
+ ## 🌐 HexaGate Platform
1170
1186
 
1171
1187
  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:
1172
1188
 
@@ -1194,10 +1210,10 @@ Endpoints:
1194
1210
  - `GET /v1/projects/:id/agents` — list agents with their YAMLs
1195
1211
  - `GET /v1/projects/:id/agents/:name` — read one agent
1196
1212
  - `PUT /v1/projects/:id/agents/:name` — save agent / policy / system YAMLs
1197
- - `WS /v1/projects/:id/serve` — producer socket (the `fortify serve` CLI dials here)
1213
+ - `WS /v1/projects/:id/serve` — producer socket (the `hexgate serve` CLI dials here)
1198
1214
  - `WS /v1/projects/:id/chat` — consumer socket (the dashboard Playground dials here)
1199
1215
 
1200
- DB lives at `platform/api/fortify.db`. Delete it and restart to wipe state.
1216
+ DB lives at `platform/api/hexgate.db`. Delete it and restart to wipe state.
1201
1217
 
1202
1218
  ### Dashboard (`platform/dashboard/`)
1203
1219
 
@@ -1220,23 +1236,23 @@ Routes:
1220
1236
 
1221
1237
  The dev server proxies `/v1/*` (HTTP and WebSocket) to `localhost:8000`, so HMR works through the same origin as the API.
1222
1238
 
1223
- ### Serve Mode (`fortify serve`)
1239
+ ### Serve Mode (`hexgate serve`)
1224
1240
 
1225
1241
  Bridges your local agent runtime to the dashboard via the platform's WebSocket relay — same pattern as Cloudflare Tunnel or ngrok.
1226
1242
 
1227
1243
  ```bash
1228
1244
  # in asianf/.env
1229
- FORTIFY_KEY=fty_live_<project>_<biscuit>
1230
- FORTIFY_API_URL=http://localhost:8000 # optional, defaults to localhost:8000
1245
+ HEXGATE_KEY=fty_live_<project>_<biscuit>
1246
+ HEXGATE_API_URL=http://localhost:8000 # optional, defaults to localhost:8000
1231
1247
 
1232
1248
  # pick an agent module:attr — uvicorn-style spec
1233
- uv run fortify serve examples.customer_bot:agent
1249
+ uv run hexgate serve examples.customer_bot:agent
1234
1250
  ```
1235
1251
 
1236
1252
  Behaviour:
1237
1253
 
1238
1254
  - Loads the agent object from the `module:attr` spec — same form as
1239
- `fortify register`. The agent's name, tools, model, and system
1255
+ `hexgate register`. The agent's name, tools, model, and system
1240
1256
  prompt come from the object directly (no flags duplicating
1241
1257
  what's already in code).
1242
1258
  - Auto-registers the manifest on first run via `POST /v1/agents`
@@ -1245,10 +1261,10 @@ Behaviour:
1245
1261
  - Fetches the operator's policy from `GET /v1/agents/{name}`. Local
1246
1262
  code is authoritative for code; the platform is authoritative for
1247
1263
  policy.
1248
- - Connects `wss://${FORTIFY_API_URL}/v1/serve` with the bearer
1264
+ - Connects `wss://${HEXGATE_API_URL}/v1/serve` with the bearer
1249
1265
  percent-encoded into the WebSocket subprotocol (Phase 6 — the WS
1250
1266
  handshake grammar doesn't allow `=` padding in plain headers).
1251
- Server echoes `fortify.v1` to confirm the contract.
1267
+ Server echoes `hexgate.v1` to confirm the contract.
1252
1268
  - Sends a `hello` frame announcing the agent name (the dashboard's
1253
1269
  "Serving" indicator reads this).
1254
1270
  - On each inbound `chat` message, **refreshes the active policy**
@@ -1262,30 +1278,30 @@ Behaviour:
1262
1278
  serve mode for prompts (planned: dashboard-side approval UI).
1263
1279
  - Reconnects with exponential backoff on socket drop.
1264
1280
 
1265
- There's no longer a `FORTIFY_AGENT_NAME` env var, `--agent` flag, or
1281
+ There's no longer a `HEXGATE_AGENT_NAME` env var, `--agent` flag, or
1266
1282
  `--use` flag — the spec carries everything. If you've been setting
1267
- `FORTIFY_AGENT_NAME` in `.env`, drop it.
1283
+ `HEXGATE_AGENT_NAME` in `.env`, drop it.
1268
1284
 
1269
- ### How `load_agent()` resolves with `FORTIFY_KEY`
1285
+ ### How `load_agent()` resolves with `HEXGATE_KEY`
1270
1286
 
1271
1287
  ```python
1272
- from fortify import load_agent
1288
+ from hexgate import load_agent
1273
1289
 
1274
1290
  agent, handler = load_agent("read_only") # explicit name required
1275
1291
  ```
1276
1292
 
1277
- When `FORTIFY_KEY` is set, `load_agent(name)` fetches the named agent from
1278
- the platform (via `load_fortify_agent`). When `FORTIFY_KEY` is not set, it
1293
+ When `HEXGATE_KEY` is set, `load_agent(name)` fetches the named agent from
1294
+ the platform (via `load_hexgate_agent`). When `HEXGATE_KEY` is not set, it
1279
1295
  falls back to local / registered / builtin resolution — no platform call.
1280
1296
 
1281
- The legacy `FORTIFY_AGENT_NAME` env-var fallback was removed in Phase 7;
1282
- direct callers of `load_fortify_agent` / `load_agent` must pass an
1283
- explicit name. For the CLI workflow, `fortify serve <module:attr>` derives
1297
+ The legacy `HEXGATE_AGENT_NAME` env-var fallback was removed in Phase 7;
1298
+ direct callers of `load_hexgate_agent` / `load_agent` must pass an
1299
+ explicit name. For the CLI workflow, `hexgate serve <module:attr>` derives
1284
1300
  the name from the loaded agent's `.name` attribute — no env var needed.
1285
1301
 
1286
1302
  ## 👤 User Scope + Roles
1287
1303
 
1288
- Real backends serve many users, and different users get different capabilities. Fortify splits that into two pieces:
1304
+ Real backends serve many users, and different users get different capabilities. HexaGate splits that into two pieces:
1289
1305
 
1290
1306
  - **`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.
1291
1307
  - **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`.
@@ -1295,9 +1311,9 @@ The two are deliberately decoupled: tokens carry **identity** (who is calling),
1295
1311
  ### Minimal example
1296
1312
 
1297
1313
  ```python
1298
- from fortify import User, load_fortify_agent, stream_agent
1314
+ from hexgate import User, load_hexgate_agent, stream_agent
1299
1315
 
1300
- agent, handler = load_fortify_agent("support-bot") # client + roles attached at load
1316
+ agent, handler = load_hexgate_agent("support-bot") # client + roles attached at load
1301
1317
 
1302
1318
  async with User(user_id="alice", role="billing", ttl_seconds=300):
1303
1319
  async for event in stream_agent(agent, handler, "refund customer 30"):
@@ -1312,10 +1328,10 @@ The cleanest production shape — set the scope once in middleware, every endpoi
1312
1328
 
1313
1329
  ```python
1314
1330
  from fastapi import FastAPI
1315
- from fortify import User, load_fortify_agent, stream_agent
1331
+ from hexgate import User, load_hexgate_agent, stream_agent
1316
1332
 
1317
1333
  app = FastAPI()
1318
- agent, handler = load_fortify_agent("support-bot") # at startup
1334
+ agent, handler = load_hexgate_agent("support-bot") # at startup
1319
1335
 
1320
1336
  @app.middleware("http")
1321
1337
  async def attach_user(request, call_next):
@@ -1415,7 +1431,7 @@ Switch to `User(user_id="alice", role="default")` and `refund_order` itself is m
1415
1431
 
1416
1432
  - **Single-file policies still work.** A legacy `policy.yaml` is treated as the `default` role — no migration needed for agents that don't yet differentiate by role.
1417
1433
  - **Lazy attenuation.** `User.__aenter__` only pushes a contextvar — the cryptographic work happens inside `stream_agent` / `invoke_agent` the first time the agent runs. Errors surface at first agent call, not at scope entry.
1418
- - **Local agents skip attenuation.** A `User` scope around a `load_local_agent` / `load_builtin_agent` agent logs a warning and runs with no facts. The `default` policy still applies — use `load_fortify_agent` for the full signed chain.
1434
+ - **Local agents skip attenuation.** A `User` scope around a `load_local_agent` / `load_builtin_agent` agent logs a warning and runs with no facts. The `default` policy still applies — use `load_hexgate_agent` for the full signed chain.
1419
1435
  - **Explicit override.** Passing `tool_use_context=` explicitly to `stream_agent` / `invoke_agent` wins over an active `User` scope. Useful for tests or one-off bypass.
1420
1436
  - **Sync callers.** `User` exposes both `async with user:` and `user.sync_scope()`. The async form is the primary API (room for KMS / audit / JWKS I/O in `__aenter__` / `__aexit__` later); the sync mirror exists for CLI loops and `Runner.run_sync`-style callers where the async ctxmgr protocol is unavailable.
1421
1437
 
@@ -1424,7 +1440,7 @@ Switch to `User(user_id="alice", role="default")` and `refund_order` itself is m
1424
1440
  For direct Python usage, the simplest runtime path is:
1425
1441
 
1426
1442
  ```python
1427
- from fortify import stream_agent
1443
+ from hexgate import stream_agent
1428
1444
 
1429
1445
  async for event in stream_agent(agent, handler, "latest AI breakthroughs"):
1430
1446
  ...