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.
- toolboundary-0.1.0/.github/workflows/ci.yml +63 -0
- toolboundary-0.1.0/.github/workflows/publish.yml +34 -0
- toolboundary-0.1.0/.gitignore +35 -0
- toolboundary-0.1.0/CONTRIBUTING.md +95 -0
- toolboundary-0.1.0/LICENSE +21 -0
- toolboundary-0.1.0/PKG-INFO +252 -0
- toolboundary-0.1.0/README.md +219 -0
- toolboundary-0.1.0/SECURITY.md +39 -0
- toolboundary-0.1.0/docs/API.md +273 -0
- toolboundary-0.1.0/examples/basic_usage.py +88 -0
- toolboundary-0.1.0/examples/benchmark.py +90 -0
- toolboundary-0.1.0/examples/network_enforcement.py +112 -0
- toolboundary-0.1.0/pyproject.toml +79 -0
- toolboundary-0.1.0/src/toolboundary/__init__.py +80 -0
- toolboundary-0.1.0/src/toolboundary/_rate_limiter.py +62 -0
- toolboundary-0.1.0/src/toolboundary/audit.py +169 -0
- toolboundary-0.1.0/src/toolboundary/boundary.py +505 -0
- toolboundary-0.1.0/src/toolboundary/decorators.py +117 -0
- toolboundary-0.1.0/src/toolboundary/enums.py +58 -0
- toolboundary-0.1.0/src/toolboundary/exceptions.py +88 -0
- toolboundary-0.1.0/src/toolboundary/integrations/__init__.py +8 -0
- toolboundary-0.1.0/src/toolboundary/integrations/langchain.py +139 -0
- toolboundary-0.1.0/src/toolboundary/network.py +275 -0
- toolboundary-0.1.0/src/toolboundary/permissions.py +71 -0
- toolboundary-0.1.0/src/toolboundary/tokens.py +215 -0
- toolboundary-0.1.0/tests/test_audit.py +75 -0
- toolboundary-0.1.0/tests/test_boundary.py +238 -0
- toolboundary-0.1.0/tests/test_boundary_network_integration.py +55 -0
- toolboundary-0.1.0/tests/test_decorators.py +70 -0
- toolboundary-0.1.0/tests/test_langchain_integration.py +92 -0
- toolboundary-0.1.0/tests/test_network.py +159 -0
- 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
|
+
[](https://pypi.org/project/toolboundary/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](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).
|