toolboundary 0.1.0__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 (32) hide show
  1. toolboundary-0.1.0/.github/workflows/ci.yml +63 -0
  2. toolboundary-0.1.0/.github/workflows/publish.yml +34 -0
  3. toolboundary-0.1.0/.gitignore +35 -0
  4. toolboundary-0.1.0/CONTRIBUTING.md +95 -0
  5. toolboundary-0.1.0/LICENSE +21 -0
  6. toolboundary-0.1.0/PKG-INFO +252 -0
  7. toolboundary-0.1.0/README.md +219 -0
  8. toolboundary-0.1.0/SECURITY.md +39 -0
  9. toolboundary-0.1.0/docs/API.md +273 -0
  10. toolboundary-0.1.0/examples/basic_usage.py +88 -0
  11. toolboundary-0.1.0/examples/benchmark.py +90 -0
  12. toolboundary-0.1.0/examples/network_enforcement.py +112 -0
  13. toolboundary-0.1.0/pyproject.toml +79 -0
  14. toolboundary-0.1.0/src/toolboundary/__init__.py +80 -0
  15. toolboundary-0.1.0/src/toolboundary/_rate_limiter.py +62 -0
  16. toolboundary-0.1.0/src/toolboundary/audit.py +169 -0
  17. toolboundary-0.1.0/src/toolboundary/boundary.py +505 -0
  18. toolboundary-0.1.0/src/toolboundary/decorators.py +117 -0
  19. toolboundary-0.1.0/src/toolboundary/enums.py +58 -0
  20. toolboundary-0.1.0/src/toolboundary/exceptions.py +88 -0
  21. toolboundary-0.1.0/src/toolboundary/integrations/__init__.py +8 -0
  22. toolboundary-0.1.0/src/toolboundary/integrations/langchain.py +139 -0
  23. toolboundary-0.1.0/src/toolboundary/network.py +275 -0
  24. toolboundary-0.1.0/src/toolboundary/permissions.py +71 -0
  25. toolboundary-0.1.0/src/toolboundary/tokens.py +215 -0
  26. toolboundary-0.1.0/tests/test_audit.py +75 -0
  27. toolboundary-0.1.0/tests/test_boundary.py +238 -0
  28. toolboundary-0.1.0/tests/test_boundary_network_integration.py +55 -0
  29. toolboundary-0.1.0/tests/test_decorators.py +70 -0
  30. toolboundary-0.1.0/tests/test_langchain_integration.py +92 -0
  31. toolboundary-0.1.0/tests/test_network.py +159 -0
  32. toolboundary-0.1.0/tests/test_tokens.py +97 -0
@@ -0,0 +1,63 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package with dev + langchain extras
25
+ run: pip install -e ".[dev,langchain]"
26
+
27
+ - name: Lint with ruff
28
+ run: ruff check src tests
29
+
30
+ - name: Run tests with coverage
31
+ run: pytest --cov=toolboundary --cov-report=xml --cov-report=term-missing
32
+
33
+ - name: Upload coverage to Codecov
34
+ uses: codecov/codecov-action@v4
35
+ with:
36
+ files: ./coverage.xml
37
+ fail_ci_if_error: false
38
+
39
+ build:
40
+ runs-on: ubuntu-latest
41
+ needs: test
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - name: Set up Python
46
+ uses: actions/setup-python@v5
47
+ with:
48
+ python-version: "3.12"
49
+
50
+ - name: Install build tooling
51
+ run: pip install build twine
52
+
53
+ - name: Build sdist and wheel
54
+ run: python -m build
55
+
56
+ - name: Check package with twine
57
+ run: twine check dist/*
58
+
59
+ - name: Upload build artifacts
60
+ uses: actions/upload-artifact@v4
61
+ with:
62
+ name: dist
63
+ path: dist/
@@ -0,0 +1,34 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # required for PyPI trusted publishing
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install build tooling
23
+ run: pip install build
24
+
25
+ - name: Build sdist and wheel
26
+ run: python -m build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
30
+ # Uses PyPI "Trusted Publishing" (OIDC) -- no API token stored in
31
+ # GitHub secrets required. Configure this repo/workflow as a
32
+ # trusted publisher at https://pypi.org/manage/project/toolboundary/settings/publishing/
33
+ # after your first manual upload, or use a PYPI_API_TOKEN secret
34
+ # instead if you prefer the classic token flow.
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ .Python
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Testing / coverage
17
+ .pytest_cache/
18
+ .coverage
19
+ .coverage.*
20
+ coverage.xml
21
+ htmlcov/
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+
25
+ # Editors
26
+ .vscode/
27
+ .idea/
28
+ *.swp
29
+
30
+ # OS
31
+ .DS_Store
32
+
33
+ # AgentGuard runtime artifacts (audit logs from examples/local testing)
34
+ *.jsonl
35
+ agentguard-audit.jsonl
@@ -0,0 +1,95 @@
1
+ # Contributing to ToolBoundary
2
+
3
+ Thanks for considering a contribution. ToolBoundary is intentionally small in
4
+ scope — please read the "Known Limitations" section of the README before
5
+ proposing a feature, to check it fits the project's design philosophy
6
+ (no required infrastructure, fail-closed by default, framework-agnostic
7
+ core with optional integrations).
8
+
9
+ ## Development setup
10
+
11
+ ```bash
12
+ git clone https://github.com/Jaycubic/toolboundary.git
13
+ cd toolboundary
14
+ python -m venv .venv
15
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
16
+ pip install -e ".[dev,langchain]"
17
+ ```
18
+
19
+ ## Running tests
20
+
21
+ ```bash
22
+ pytest # full suite with coverage report
23
+ pytest tests/test_boundary.py # a single file
24
+ pytest -k kill_switch # tests matching a keyword
25
+ ```
26
+
27
+ All new code should include tests. We aim to keep coverage above 90%.
28
+
29
+ ## Linting
30
+
31
+ ```bash
32
+ ruff check src tests
33
+ mypy src
34
+ ```
35
+
36
+ ## Project structure
37
+
38
+ ```
39
+ src/toolboundary/
40
+ __init__.py # public API surface
41
+ boundary.py # core Boundary decision engine
42
+ permissions.py # ToolPermission
43
+ enums.py # AutonomyLevel, AccessMode, etc.
44
+ exceptions.py # BoundaryViolation, ApprovalRequired, etc.
45
+ audit.py # AuditTrail and sinks (Logging, JSONL, Webhook)
46
+ tokens.py # AuthorizationToken, TokenIssuer (network enforcement)
47
+ network.py # NetworkEnforcer proxy (optional, stdlib-only)
48
+ _rate_limiter.py # internal sliding-window rate limiter
49
+ decorators.py # @guarded_tool
50
+ integrations/
51
+ langchain.py # LangChain BaseTool wrapping
52
+ ```
53
+
54
+ ## Good first contributions
55
+
56
+ These are scoped, valuable, and don't require redesigning anything:
57
+
58
+ - **Redis-backed rate limiter / token store** — for multi-process
59
+ deployments. Implement the same interface as `SlidingWindowRateLimiter`
60
+ and `InMemoryTokenStore` and submit as an optional extra
61
+ (`toolboundary[redis]`).
62
+ - **CrewAI / AutoGen / LangGraph integrations** — mirror the structure of
63
+ `integrations/langchain.py`: wrap the framework's actual tool-execution
64
+ call site, not just provide a decorator the user has to remember to apply.
65
+ - **A minimal local dashboard** — a single-file script that tails a
66
+ `JSONLFileSink` log and renders a simple live view. Should have zero
67
+ required dependencies beyond the standard library, in keeping with the
68
+ project's "no infrastructure required" philosophy — a `flask`/`fastapi`-based
69
+ version is welcome too, but should be a clearly optional extra, not folded
70
+ into core.
71
+ - **More policy hook examples** — e.g. a time-of-day hook, a
72
+ geo/IP-based hook, or an example calling out to an external policy
73
+ engine.
74
+
75
+ ## Pull request guidelines
76
+
77
+ 1. Open an issue first for anything beyond a small bugfix, so we can agree
78
+ on the approach before you invest time.
79
+ 2. Keep the core package (`toolboundary/__init__.py` and everything it
80
+ imports by default) dependency-free. New framework integrations belong
81
+ under `integrations/` with their own optional extra in `pyproject.toml`.
82
+ 3. Match the existing docstring style — every public class/function should
83
+ explain *why*, not just *what*, especially around security-relevant
84
+ decisions (fail-open vs fail-closed, what a check does and doesn't cover).
85
+ 4. Add tests that would fail without your change.
86
+
87
+ ## Reporting security issues
88
+
89
+ Please do not open a public GitHub issue for a security vulnerability.
90
+ See `SECURITY.md` for how to report privately.
91
+
92
+ ## Code of conduct
93
+
94
+ Be respectful. Assume good faith. This is a small project maintained in
95
+ someone's spare time — patience with review turnaround is appreciated.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Geoffrey (Jaycubic)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,252 @@
1
+ Metadata-Version: 2.5
2
+ Name: toolboundary
3
+ Version: 0.1.0
4
+ Summary: Runtime boundary enforcement for AI agents -- as a library, not a service.
5
+ Project-URL: Homepage, https://github.com/Jaycubic/toolboundary
6
+ Project-URL: Repository, https://github.com/Jaycubic/toolboundary
7
+ Project-URL: Issues, https://github.com/Jaycubic/toolboundary/issues
8
+ Author-email: Jofrey John Joseph <jofreyjohnmrutu01@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent-security,ai-agents,ai-governance,guardrails,langchain,llm,tool-calling,zero-trust
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Provides-Extra: dev
24
+ Requires-Dist: langchain-core>=0.1.0; extra == 'dev'
25
+ Requires-Dist: mypy>=1.8; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
27
+ Requires-Dist: pytest>=7.4; extra == 'dev'
28
+ Requires-Dist: ruff>=0.4; extra == 'dev'
29
+ Provides-Extra: langchain
30
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
31
+ Provides-Extra: network
32
+ Description-Content-Type: text/markdown
33
+
34
+ # ToolBoundary
35
+
36
+ **Runtime boundary enforcement for AI agents — as a library, not a service.**
37
+
38
+ [![PyPI](https://img.shields.io/badge/pypi-v0.1.0-blue)](https://pypi.org/project/toolboundary/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
40
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
41
+
42
+ ToolBoundary answers one question, fast and locally, every time your agent tries to call a
43
+ tool: **"is this exact call allowed, right now?"**
44
+
45
+ No separate web app. No database to stand up. No dashboard to log into. No subscription.
46
+ Your policy is plain Python, version-controlled with the rest of your code.
47
+
48
+ ```bash
49
+ pip install toolboundary
50
+ ```
51
+
52
+ ## Why this exists
53
+
54
+ Enterprise AI-governance platforms (agent registries, policy engines, approval
55
+ dashboards) make sense when a large organization has dozens of AI agents built by
56
+ different teams and needs a compliance layer to track all of them. That's real
57
+ infrastructure for a real problem — but it's disproportionate for the much more common
58
+ case: **one developer or a small team building one to a handful of agents**, who just
59
+ need to make sure a tool-calling agent can't do something catastrophic.
60
+
61
+ ToolBoundary is built for that second case. It costs nothing, requires no
62
+ infrastructure, and takes minutes to add to an existing agent.
63
+
64
+ ## Quickstart
65
+
66
+ ```python
67
+ from toolboundary import Boundary, ToolPermission, AutonomyLevel, AccessMode
68
+
69
+ boundary = Boundary(
70
+ agent_name="support-agent",
71
+ autonomy=AutonomyLevel.LIMITED_AUTONOMOUS,
72
+ permissions=[
73
+ ToolPermission("read_ticket_db", access_mode=AccessMode.READ_ONLY),
74
+ ToolPermission(
75
+ "send_reply_email",
76
+ access_mode=AccessMode.EXECUTE,
77
+ max_calls_per_hour=30,
78
+ ),
79
+ ],
80
+ blocked_operations=frozenset({"delete_ticket"}),
81
+ max_actions_per_hour=100,
82
+ kill_switch_env="TOOLBOUNDARY_KILL_SWITCH",
83
+ )
84
+
85
+ # Somewhere in your agent's tool-calling code:
86
+ boundary.check("read_ticket_db", access_mode=AccessMode.READ_ONLY) # passes silently
87
+ boundary.check("delete_ticket", operation="delete_ticket") # raises BoundaryViolation
88
+ ```
89
+
90
+ If a call is denied, `boundary.check(...)` raises `BoundaryViolation` (or a more
91
+ specific subclass like `KillSwitchActive` or `RateLimitExceeded`). If a call needs a
92
+ human before it can proceed, it raises `ApprovalRequired`. Every decision — allow,
93
+ deny, or approval-required — is written to a structured audit log automatically.
94
+
95
+ ### Emergency stop
96
+
97
+ ```bash
98
+ export TOOLBOUNDARY_KILL_SWITCH=1
99
+ ```
100
+
101
+ Set the environment variable your `Boundary` was configured with, and every future
102
+ call for that agent is denied immediately — no restart required, no code change,
103
+ no separate dashboard to log into.
104
+
105
+ ## Two ways to enforce the boundary
106
+
107
+ ### 1. Decorator (plain Python functions)
108
+
109
+ ```python
110
+ from toolboundary import guarded_tool, AccessMode
111
+
112
+ @guarded_tool(boundary, access_mode=AccessMode.EXECUTE, value_arg="amount")
113
+ def wire_transfer(account_id: str, amount: float) -> str:
114
+ return f"transferred {amount} to {account_id}"
115
+
116
+ wire_transfer(account_id="acct_1", amount=250_000)
117
+ # raises BoundaryViolation if 250_000 exceeds the permission's max_value —
118
+ # the function body never executes.
119
+ ```
120
+
121
+ Once a function is decorated, calling it *is* calling through ToolBoundary. There is no
122
+ code path to the real implementation that skips the check.
123
+
124
+ ### 2. LangChain tools
125
+
126
+ ```python
127
+ from toolboundary.integrations.langchain import guard_tools
128
+ from toolboundary import AccessMode
129
+
130
+ guarded_tools = guard_tools(
131
+ [read_db_tool, send_email_tool, wire_transfer_tool],
132
+ boundary,
133
+ default_access_mode=AccessMode.READ_ONLY,
134
+ overrides={
135
+ "send_email_tool": {"access_mode": AccessMode.EXECUTE},
136
+ "wire_transfer_tool": {"access_mode": AccessMode.EXECUTE, "value_arg": "amount"},
137
+ },
138
+ )
139
+
140
+ agent_executor = AgentExecutor(agent=agent, tools=guarded_tools)
141
+ ```
142
+
143
+ This wraps the LangChain `BaseTool` objects themselves — the objects your
144
+ `AgentExecutor` actually invokes when the LLM decides to call a tool — so the boundary
145
+ check runs inside LangChain's own tool-execution path, not as a step the agent's
146
+ reasoning loop has to remember to call.
147
+
148
+ Install with the LangChain extra: `pip install toolboundary[langchain]`
149
+
150
+ ## What a `Boundary` can enforce
151
+
152
+ | Control | Example |
153
+ |---|---|
154
+ | Which tools an agent may use at all | `permissions=[ToolPermission("read_db", ...)]` |
155
+ | Operation-level allow/block lists | `blocked_operations=frozenset({"delete_customer"})` |
156
+ | Access mode (READ_ONLY / WRITE / EXECUTE / ADMIN) | `access_mode=AccessMode.EXECUTE` |
157
+ | Transaction value ceilings | `ToolPermission(..., max_value=500_000)` |
158
+ | Record-count ceilings | `ToolPermission(..., max_records=100)` |
159
+ | Rate limits (global or per-tool) | `max_actions_per_hour=60` |
160
+ | Autonomy level | `AutonomyLevel.RECOMMEND_ONLY` / `HUMAN_APPROVAL_REQUIRED` / `LIMITED_AUTONOMOUS` / `AUTONOMOUS` / `QUARANTINED` |
161
+ | Time-bounded validity | `valid_from=`, `valid_to=` |
162
+ | Environment restriction | `allowed_environments=frozenset({"DEV", "TEST"})` |
163
+ | Emergency kill switch | in-process flag or environment variable |
164
+ | Custom policy logic | `policy_hooks=[my_custom_check]` |
165
+
166
+ Full field reference: see [`docs/API.md`](docs/API.md).
167
+
168
+ ## Audit trail
169
+
170
+ Every decision produces a structured event. By default it goes to Python's standard
171
+ `logging` module under the logger name `toolboundary.audit`, so it flows into whatever
172
+ logging pipeline you already have (stdout, a file, CloudWatch, Datadog, etc.) with zero
173
+ extra code.
174
+
175
+ ```python
176
+ from toolboundary.audit import AuditTrail, JSONLFileSink
177
+
178
+ boundary = Boundary(
179
+ agent_name="support-agent",
180
+ ...,
181
+ audit=AuditTrail(sinks=[JSONLFileSink("toolboundary-audit.jsonl")]),
182
+ )
183
+ ```
184
+
185
+ A `WebhookSink` is also included if you want to forward events to a self-hosted
186
+ dashboard or a centralized governance platform. Audit delivery is always best-effort —
187
+ a network hiccup in your audit pipeline can never block or crash your agent, because
188
+ the ALLOW/DENY decision has already been enforced locally before the sink is invoked.
189
+
190
+ ## Design philosophy
191
+
192
+ - **Fail closed.** Anything ambiguous, misconfigured, or erroring is treated as denied
193
+ by default. See `fail_closed_on_hook_error` for the one place this is configurable.
194
+ - **No infrastructure required.** No database, no server, no login. The whole thing is
195
+ a Python object you construct alongside your agent code.
196
+ - **Version-controlled policy.** Your boundary is code, reviewed in the same pull
197
+ requests as everything else — not a setting buried in a web UI that drifts silently
198
+ out of sync with what the agent actually does.
199
+ - **Loud by default.** Denials raise exceptions, not silent `False` returns that are
200
+ easy to accidentally ignore.
201
+ - **Framework-agnostic core, framework-specific adapters.** The core `Boundary` has
202
+ zero dependencies. Framework integrations (LangChain today; more welcome via PR) are
203
+ optional extras.
204
+
205
+ ## Known limitations — please read this
206
+
207
+ ToolBoundary is an **in-process, application-layer** library. Being explicit about what
208
+ it does *not* do is more important than what it does:
209
+
210
+ - **It cannot stop an agent that bypasses it entirely.** If your agent's code has any
211
+ path that calls a tool's real implementation directly — instead of through a
212
+ `@guarded_tool`-wrapped function or a `guard_tool`-wrapped LangChain tool — that call
213
+ is not evaluated. ToolBoundary governs the doors you route through it; it is not a
214
+ network firewall.
215
+ - **It is not a substitute for credential scoping.** If the underlying API key or
216
+ database credential your tool uses has broader permissions than ToolBoundary's policy
217
+ allows, a determined attacker who obtains that credential directly bypasses
218
+ ToolBoundary entirely. Scope your actual credentials as tightly as you can — ToolBoundary
219
+ is a second layer, not a replacement for the first.
220
+ - **It is not a compliance/audit system of record for large organizations.** If you
221
+ have dozens of agents across multiple teams and need human governance workflows,
222
+ cross-team registries, and formal approval routing, look at enterprise AI governance
223
+ platforms — ToolBoundary is intentionally not trying to be that.
224
+ - **The in-memory rate limiter is per-process.** If you run multiple replicas of your
225
+ agent, each process has its own rate-limit counters unless you supply a shared
226
+ backing store (see `Boundary`'s internals / open an issue if you need this — a
227
+ Redis-backed limiter is a natural community contribution).
228
+
229
+ If your threat model requires guaranteeing that a compromised agent *physically
230
+ cannot* reach a tool's network endpoint except through an approved path, you need a
231
+ network-layer control (a sidecar proxy, egress firewall rule, or service mesh policy)
232
+ in addition to ToolBoundary, not instead of it.
233
+
234
+ ## Installation
235
+
236
+ ```bash
237
+ pip install toolboundary # core, zero dependencies
238
+ pip install toolboundary[langchain] # + LangChain integration
239
+ ```
240
+
241
+ ## Contributing
242
+
243
+ Issues and PRs are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
244
+
245
+ Ideas that would make great first contributions:
246
+ - Redis-backed rate limiter for multi-process deployments
247
+ - CrewAI / AutoGen / LangGraph integrations (mirroring `integrations/langchain.py`)
248
+ - A minimal read-only local dashboard that tails a `JSONLFileSink` log
249
+
250
+ ## License
251
+
252
+ MIT — see [`LICENSE`](LICENSE).