runbound 0.3.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 (110) hide show
  1. runbound-0.3.0/LICENSE +21 -0
  2. runbound-0.3.0/PKG-INFO +198 -0
  3. runbound-0.3.0/README.md +168 -0
  4. runbound-0.3.0/pyproject.toml +48 -0
  5. runbound-0.3.0/runbound/__init__.py +89 -0
  6. runbound-0.3.0/runbound/_coverage.py +612 -0
  7. runbound-0.3.0/runbound/alerts.py +149 -0
  8. runbound-0.3.0/runbound/api.py +3016 -0
  9. runbound-0.3.0/runbound/autowrap.py +159 -0
  10. runbound-0.3.0/runbound/circuit.py +464 -0
  11. runbound-0.3.0/runbound/config.py +927 -0
  12. runbound-0.3.0/runbound/detectors.py +1118 -0
  13. runbound-0.3.0/runbound/engine.py +1657 -0
  14. runbound-0.3.0/runbound/events.py +152 -0
  15. runbound-0.3.0/runbound/exceptions.py +67 -0
  16. runbound-0.3.0/runbound/export.py +456 -0
  17. runbound-0.3.0/runbound/integrations/__init__.py +6 -0
  18. runbound-0.3.0/runbound/integrations/langchain.py +237 -0
  19. runbound-0.3.0/runbound/ladder.py +242 -0
  20. runbound-0.3.0/runbound/plane.py +329 -0
  21. runbound-0.3.0/runbound/plane_types.py +582 -0
  22. runbound-0.3.0/runbound/policy.py +684 -0
  23. runbound-0.3.0/runbound/pricing.py +425 -0
  24. runbound-0.3.0/runbound/quota.py +392 -0
  25. runbound-0.3.0/runbound/responses.py +381 -0
  26. runbound-0.3.0/runbound/shared.py +1370 -0
  27. runbound-0.3.0/runbound/state.py +383 -0
  28. runbound-0.3.0/runbound/wrappers/__init__.py +1385 -0
  29. runbound-0.3.0/runbound/wrappers/anthropic_wrapper.py +458 -0
  30. runbound-0.3.0/runbound/wrappers/openai_wrapper.py +690 -0
  31. runbound-0.3.0/runbound.egg-info/PKG-INFO +198 -0
  32. runbound-0.3.0/runbound.egg-info/SOURCES.txt +108 -0
  33. runbound-0.3.0/runbound.egg-info/dependency_links.txt +1 -0
  34. runbound-0.3.0/runbound.egg-info/requires.txt +9 -0
  35. runbound-0.3.0/runbound.egg-info/top_level.txt +1 -0
  36. runbound-0.3.0/setup.cfg +4 -0
  37. runbound-0.3.0/tests/test_admission.py +605 -0
  38. runbound-0.3.0/tests/test_alert_lifecycle.py +504 -0
  39. runbound-0.3.0/tests/test_anomaly_identity.py +206 -0
  40. runbound-0.3.0/tests/test_anthropic_thinking.py +96 -0
  41. runbound-0.3.0/tests/test_api.py +362 -0
  42. runbound-0.3.0/tests/test_async_throttle.py +182 -0
  43. runbound-0.3.0/tests/test_async_tool.py +218 -0
  44. runbound-0.3.0/tests/test_async_wrappers.py +288 -0
  45. runbound-0.3.0/tests/test_autowrap.py +481 -0
  46. runbound-0.3.0/tests/test_circuit.py +315 -0
  47. runbound-0.3.0/tests/test_circuit_force.py +297 -0
  48. runbound-0.3.0/tests/test_circuit_quota.py +691 -0
  49. runbound-0.3.0/tests/test_config.py +196 -0
  50. runbound-0.3.0/tests/test_config_plane.py +229 -0
  51. runbound-0.3.0/tests/test_coverage.py +429 -0
  52. runbound-0.3.0/tests/test_coverage_warnings.py +640 -0
  53. runbound-0.3.0/tests/test_detector_priority.py +158 -0
  54. runbound-0.3.0/tests/test_detectors.py +740 -0
  55. runbound-0.3.0/tests/test_docs_examples.py +233 -0
  56. runbound-0.3.0/tests/test_engine.py +217 -0
  57. runbound-0.3.0/tests/test_engine_observers.py +533 -0
  58. runbound-0.3.0/tests/test_error_storm.py +512 -0
  59. runbound-0.3.0/tests/test_export.py +491 -0
  60. runbound-0.3.0/tests/test_export_requeue.py +330 -0
  61. runbound-0.3.0/tests/test_fanout.py +352 -0
  62. runbound-0.3.0/tests/test_fleet_fixes.py +370 -0
  63. runbound-0.3.0/tests/test_hash_salt.py +121 -0
  64. runbound-0.3.0/tests/test_ladder.py +295 -0
  65. runbound-0.3.0/tests/test_ladder_api.py +471 -0
  66. runbound-0.3.0/tests/test_ladder_baseline.py +231 -0
  67. runbound-0.3.0/tests/test_ladder_detector.py +607 -0
  68. runbound-0.3.0/tests/test_ladder_why.py +438 -0
  69. runbound-0.3.0/tests/test_langchain.py +404 -0
  70. runbound-0.3.0/tests/test_latch.py +584 -0
  71. runbound-0.3.0/tests/test_latch_ttl.py +458 -0
  72. runbound-0.3.0/tests/test_loop_policy.py +459 -0
  73. runbound-0.3.0/tests/test_loop_repeatable.py +235 -0
  74. runbound-0.3.0/tests/test_on_trip.py +91 -0
  75. runbound-0.3.0/tests/test_openai_read_tool_requests.py +45 -0
  76. runbound-0.3.0/tests/test_plane_client.py +521 -0
  77. runbound-0.3.0/tests/test_plane_loss.py +519 -0
  78. runbound-0.3.0/tests/test_plane_modes.py +263 -0
  79. runbound-0.3.0/tests/test_plane_refusal_entry.py +164 -0
  80. runbound-0.3.0/tests/test_plane_types.py +668 -0
  81. runbound-0.3.0/tests/test_policy_core.py +553 -0
  82. runbound-0.3.0/tests/test_policy_hook.py +498 -0
  83. runbound-0.3.0/tests/test_policy_merge.py +408 -0
  84. runbound-0.3.0/tests/test_policy_merge_properties.py +258 -0
  85. runbound-0.3.0/tests/test_pricing.py +480 -0
  86. runbound-0.3.0/tests/test_privacy_wire.py +397 -0
  87. runbound-0.3.0/tests/test_quota.py +468 -0
  88. runbound-0.3.0/tests/test_real_sdk.py +733 -0
  89. runbound-0.3.0/tests/test_responses.py +635 -0
  90. runbound-0.3.0/tests/test_review_fixes_t59.py +267 -0
  91. runbound-0.3.0/tests/test_selfhosted.py +928 -0
  92. runbound-0.3.0/tests/test_session_sync.py +609 -0
  93. runbound-0.3.0/tests/test_sessions.py +722 -0
  94. runbound-0.3.0/tests/test_shared_state.py +855 -0
  95. runbound-0.3.0/tests/test_spike.py +604 -0
  96. runbound-0.3.0/tests/test_spike_tuning.py +358 -0
  97. runbound-0.3.0/tests/test_state.py +185 -0
  98. runbound-0.3.0/tests/test_streaming.py +592 -0
  99. runbound-0.3.0/tests/test_streams_abandoned.py +709 -0
  100. runbound-0.3.0/tests/test_time_scopes.py +147 -0
  101. runbound-0.3.0/tests/test_timeout_costcap.py +352 -0
  102. runbound-0.3.0/tests/test_token_and_delivery.py +349 -0
  103. runbound-0.3.0/tests/test_tool_report.py +482 -0
  104. runbound-0.3.0/tests/test_tool_requests.py +711 -0
  105. runbound-0.3.0/tests/test_tool_rules.py +703 -0
  106. runbound-0.3.0/tests/test_unpriced.py +462 -0
  107. runbound-0.3.0/tests/test_wall_sdk.py +555 -0
  108. runbound-0.3.0/tests/test_wrapper_errors.py +476 -0
  109. runbound-0.3.0/tests/test_wrapper_timing.py +743 -0
  110. runbound-0.3.0/tests/test_wrappers.py +282 -0
runbound-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amanjot Singh
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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: runbound
3
+ Version: 0.3.0
4
+ Summary: Deterministic, LLM-free runaway detection and circuit breaking for AI agents.
5
+ Author: Amanjot Singh
6
+ License: MIT
7
+ Project-URL: Homepage, https://runbound.co
8
+ Project-URL: Documentation, https://runbound.co/docs
9
+ Project-URL: Repository, https://github.com/runboundai/runbound
10
+ Project-URL: Changelog, https://github.com/runboundai/runbound/blob/main/CHANGELOG.md
11
+ Keywords: ai agents,observability,circuit breaker,runaway,cost control,llm
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: System :: Monitoring
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest>=7.0; extra == "test"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Provides-Extra: langchain
28
+ Requires-Dist: langchain-core; extra == "langchain"
29
+ Dynamic: license-file
30
+
31
+ # runbound
32
+
33
+ **Runtime controls for autonomous AI agents.** runbound runs inside your
34
+ agent's process and stops runaway cost, loops, tool abuse and provider
35
+ failures deterministically, by counting, timing and hashing, never by asking a
36
+ model whether something looks wrong. It reads no prompts and no replies, it
37
+ puts no gateway in your hot path, and it never speaks to your users: when it
38
+ stops something it hands control back to you and you answer in your own voice.
39
+
40
+ This is for the platform team that owns AI agents in production, the people
41
+ who get paged when one runs away, not the people who wrote its prompt.
42
+
43
+ One agent burned **$2,847 in four hours** on a refactoring loop while every
44
+ monitoring dashboard stayed green. Nobody was watching it at 3am, and nothing
45
+ in the stack had the authority to pull the plug. runbound is the layer with
46
+ the authority to pull the plug, and the proof that it did — the whole story is
47
+ in [Why this exists](docs/concepts/why.md).
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install runbound
53
+ ```
54
+
55
+ There are no runtime dependencies — stdlib only. LangChain support is the one
56
+ extra: `pip install "runbound[langchain]"`.
57
+
58
+ ## Quick start
59
+
60
+ Guard every model call in the process:
61
+
62
+ ```python
63
+ import runbound
64
+ runbound.init(budget_usd=5.0, on_anomaly="raise") # everything below is now guarded
65
+ ```
66
+
67
+ Scope every control to one unit of work:
68
+
69
+ ```python
70
+ with runbound.session(f"run:{run_id}"):
71
+ answer = client.chat.completions.create(...)
72
+ ```
73
+
74
+ Refuse an action before its function body runs:
75
+
76
+ ```python
77
+ @runbound.tool(max_calls=1) # the rule lives on the tool, in the same diff
78
+ def issue_refund(user: str, amount: float): ...
79
+ ```
80
+
81
+ The full five-minute version — wiring all three sensors, checking what is
82
+ actually guarded, catching the trip, and the offline demos that need no API
83
+ key — is in [Getting started](docs/getting-started.md).
84
+
85
+ ## Three levels of protection
86
+
87
+ You choose how far in to go. Each level is a small, separate step, and most of
88
+ the value arrives at the first one. The full text is in
89
+ [Three levels of protection](docs/concepts/three-levels.md).
90
+
91
+ **Level 1: guard the model calls.** Two lines. `runbound.init()` wraps the
92
+ OpenAI and Anthropic clients for you (or you call `runbound.wrap(client)` on
93
+ any client with that shape). From then on every model call is counted, timed
94
+ and priced, and you get: dollar and token budgets, per-call caps, velocity
95
+ limits, step and wall-clock limits, the spike ladder, error-storm detection
96
+ with a circuit per provider, and refusal profiles that carry your own status
97
+ and sentence. All of that is what the free SDK does on its own, with no
98
+ network call and no account. Nothing is decorated, no policy is written. See
99
+ [What it controls](docs/concepts/what-it-controls.md).
100
+
101
+ **Level 2: one session per run.** One line per unit of work. Wrap each run in
102
+ `runbound.session(key)` — the key is any id you already have: a run id, a job,
103
+ a tenant, a customer — and every control above becomes per key instead of per
104
+ process: this run's budget, this run's ladder, this run stopped, every other
105
+ caller untouched. See [Runs keyed by any id](docs/guides/runs.md).
106
+
107
+ **Level 3: refuse actions before they run.** Only for the tools that matter.
108
+ Decorate the two or three functions that are irreversible or reach the outside
109
+ world with `@runbound.tool`, write a policy (deny this tool, at most once per
110
+ run, needs approval, or a predicate of your own over the arguments, run in
111
+ your process), and the call is refused *before the function body runs*. The
112
+ rules are yours; runbound enforces and records them, it never judges the
113
+ action. See [Action policy](docs/guides/policy.md).
114
+
115
+ Nothing here protects against a *bad answer*: hallucinations, prompt injection
116
+ and output quality are out of scope by design. "The agent said something
117
+ wrong" is a different product. "The agent would not stop" is this one.
118
+
119
+ ## What it never sees
120
+
121
+ - **Prompts and model replies are never read, stored, or sent** — except the
122
+ one narrow case of `estimate_tokens=True`, which counts characters when the
123
+ server reported no usage.
124
+ - **Tool arguments are sha256-hashed before storage, salted per process.** The
125
+ loop detector compares digests of `(tool_name, args, sorted kwargs)` mixed
126
+ with a random salt generated once at import, never the arguments themselves.
127
+ - **Raw arguments never leave your process.** They are not stored, not logged,
128
+ and not included in any alert payload.
129
+ - **A session key reaches a connected plane as a hash.** The plane gets
130
+ `sha256(key)` and nothing else, unless you opt in with
131
+ `send_session_keys=True`.
132
+ - **Failures are recorded as the exception's class name.** Only the
133
+ exception's class name, never its message, is what reaches the plane.
134
+ - **No network calls except the ones you configure.** No telemetry, no
135
+ phone-home, no hosted backend you did not point us at. With no `token` and
136
+ no `control_plane_url` set, runbound opens no sockets at all.
137
+
138
+ The full accounting — every number, where it comes from, and what goes blind
139
+ without each sensor — is in
140
+ [What the SDK actually sees](docs/concepts/what-it-sees.md) and
141
+ [Privacy](docs/reference/privacy.md).
142
+
143
+ ## Documentation
144
+
145
+ The manual lives in [`docs/`](docs/README.md); it is also published, with
146
+ executed snippets, at [runbound.co/docs](https://runbound.co/docs).
147
+
148
+ - **[Getting started](docs/getting-started.md)** — install, wire the three
149
+ sensors around one agent run, and check what is actually guarded.
150
+ - **[Concepts](docs/README.md#concepts)** — the three levels, why this exists,
151
+ how it works, what it controls, and exactly what the SDK sees and never
152
+ sees.
153
+ - **[Guides](docs/README.md#guides)** — keyed runs, fleet mode, spike
154
+ detection, action policy, self-hosted models, LangChain, async and
155
+ streaming.
156
+ - **[Reference](docs/README.md#reference)** — every configuration field, the
157
+ detectors, what happens when something trips, the circuit breaker, limits,
158
+ the compatibility matrix, privacy, and the guarantees.
159
+
160
+ [Roadmap](docs/roadmap.md) is what is shipped, what is still open, and what is
161
+ explicitly out of scope.
162
+
163
+ ## Getting help
164
+
165
+ **Something not working, or a question?** Open an issue at
166
+ [github.com/runboundai/runbound/issues](https://github.com/runboundai/runbound/issues),
167
+ or email **runboundai@gmail.com** if it is not something you can post in
168
+ public.
169
+
170
+ The two things that let us answer on the first reply instead of the third:
171
+
172
+ ```python
173
+ import runbound
174
+ print(runbound.__version__)
175
+ print(runbound.coverage()) # after init() and your first guarded call
176
+ ```
177
+
178
+ `coverage()` is plain numbers and names — which provider classes were
179
+ patched, how many calls and tools runbound saw, which imported providers
180
+ nothing is guarding, and any silent-zero warning — so it shows at a glance
181
+ whether runbound can see your traffic at all, which is the most common
182
+ cause of "it isn't catching anything". It lists your decorated tools'
183
+ names; remove any you would rather not post. Please also say your Python
184
+ version, your `openai` / `anthropic` versions, and whether you run with a
185
+ control plane (`token` / `control_plane_url`) or locally.
186
+
187
+ **Never paste** an API key, a control-plane token, a prompt, a reply, or a
188
+ real session key into an issue.
189
+
190
+ **Security issues go to email, not to an issue** — see
191
+ [SECURITY.md](SECURITY.md) for what to send and the disclosure window.
192
+
193
+ ## License
194
+
195
+ MIT — see [LICENSE](LICENSE). Release history is in
196
+ [CHANGELOG.md](CHANGELOG.md). The promises above, stated as bounds with the
197
+ tests that assert them (and where nothing does yet), are in
198
+ [INVARIANTS.md](INVARIANTS.md).
@@ -0,0 +1,168 @@
1
+ # runbound
2
+
3
+ **Runtime controls for autonomous AI agents.** runbound runs inside your
4
+ agent's process and stops runaway cost, loops, tool abuse and provider
5
+ failures deterministically, by counting, timing and hashing, never by asking a
6
+ model whether something looks wrong. It reads no prompts and no replies, it
7
+ puts no gateway in your hot path, and it never speaks to your users: when it
8
+ stops something it hands control back to you and you answer in your own voice.
9
+
10
+ This is for the platform team that owns AI agents in production, the people
11
+ who get paged when one runs away, not the people who wrote its prompt.
12
+
13
+ One agent burned **$2,847 in four hours** on a refactoring loop while every
14
+ monitoring dashboard stayed green. Nobody was watching it at 3am, and nothing
15
+ in the stack had the authority to pull the plug. runbound is the layer with
16
+ the authority to pull the plug, and the proof that it did — the whole story is
17
+ in [Why this exists](docs/concepts/why.md).
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install runbound
23
+ ```
24
+
25
+ There are no runtime dependencies — stdlib only. LangChain support is the one
26
+ extra: `pip install "runbound[langchain]"`.
27
+
28
+ ## Quick start
29
+
30
+ Guard every model call in the process:
31
+
32
+ ```python
33
+ import runbound
34
+ runbound.init(budget_usd=5.0, on_anomaly="raise") # everything below is now guarded
35
+ ```
36
+
37
+ Scope every control to one unit of work:
38
+
39
+ ```python
40
+ with runbound.session(f"run:{run_id}"):
41
+ answer = client.chat.completions.create(...)
42
+ ```
43
+
44
+ Refuse an action before its function body runs:
45
+
46
+ ```python
47
+ @runbound.tool(max_calls=1) # the rule lives on the tool, in the same diff
48
+ def issue_refund(user: str, amount: float): ...
49
+ ```
50
+
51
+ The full five-minute version — wiring all three sensors, checking what is
52
+ actually guarded, catching the trip, and the offline demos that need no API
53
+ key — is in [Getting started](docs/getting-started.md).
54
+
55
+ ## Three levels of protection
56
+
57
+ You choose how far in to go. Each level is a small, separate step, and most of
58
+ the value arrives at the first one. The full text is in
59
+ [Three levels of protection](docs/concepts/three-levels.md).
60
+
61
+ **Level 1: guard the model calls.** Two lines. `runbound.init()` wraps the
62
+ OpenAI and Anthropic clients for you (or you call `runbound.wrap(client)` on
63
+ any client with that shape). From then on every model call is counted, timed
64
+ and priced, and you get: dollar and token budgets, per-call caps, velocity
65
+ limits, step and wall-clock limits, the spike ladder, error-storm detection
66
+ with a circuit per provider, and refusal profiles that carry your own status
67
+ and sentence. All of that is what the free SDK does on its own, with no
68
+ network call and no account. Nothing is decorated, no policy is written. See
69
+ [What it controls](docs/concepts/what-it-controls.md).
70
+
71
+ **Level 2: one session per run.** One line per unit of work. Wrap each run in
72
+ `runbound.session(key)` — the key is any id you already have: a run id, a job,
73
+ a tenant, a customer — and every control above becomes per key instead of per
74
+ process: this run's budget, this run's ladder, this run stopped, every other
75
+ caller untouched. See [Runs keyed by any id](docs/guides/runs.md).
76
+
77
+ **Level 3: refuse actions before they run.** Only for the tools that matter.
78
+ Decorate the two or three functions that are irreversible or reach the outside
79
+ world with `@runbound.tool`, write a policy (deny this tool, at most once per
80
+ run, needs approval, or a predicate of your own over the arguments, run in
81
+ your process), and the call is refused *before the function body runs*. The
82
+ rules are yours; runbound enforces and records them, it never judges the
83
+ action. See [Action policy](docs/guides/policy.md).
84
+
85
+ Nothing here protects against a *bad answer*: hallucinations, prompt injection
86
+ and output quality are out of scope by design. "The agent said something
87
+ wrong" is a different product. "The agent would not stop" is this one.
88
+
89
+ ## What it never sees
90
+
91
+ - **Prompts and model replies are never read, stored, or sent** — except the
92
+ one narrow case of `estimate_tokens=True`, which counts characters when the
93
+ server reported no usage.
94
+ - **Tool arguments are sha256-hashed before storage, salted per process.** The
95
+ loop detector compares digests of `(tool_name, args, sorted kwargs)` mixed
96
+ with a random salt generated once at import, never the arguments themselves.
97
+ - **Raw arguments never leave your process.** They are not stored, not logged,
98
+ and not included in any alert payload.
99
+ - **A session key reaches a connected plane as a hash.** The plane gets
100
+ `sha256(key)` and nothing else, unless you opt in with
101
+ `send_session_keys=True`.
102
+ - **Failures are recorded as the exception's class name.** Only the
103
+ exception's class name, never its message, is what reaches the plane.
104
+ - **No network calls except the ones you configure.** No telemetry, no
105
+ phone-home, no hosted backend you did not point us at. With no `token` and
106
+ no `control_plane_url` set, runbound opens no sockets at all.
107
+
108
+ The full accounting — every number, where it comes from, and what goes blind
109
+ without each sensor — is in
110
+ [What the SDK actually sees](docs/concepts/what-it-sees.md) and
111
+ [Privacy](docs/reference/privacy.md).
112
+
113
+ ## Documentation
114
+
115
+ The manual lives in [`docs/`](docs/README.md); it is also published, with
116
+ executed snippets, at [runbound.co/docs](https://runbound.co/docs).
117
+
118
+ - **[Getting started](docs/getting-started.md)** — install, wire the three
119
+ sensors around one agent run, and check what is actually guarded.
120
+ - **[Concepts](docs/README.md#concepts)** — the three levels, why this exists,
121
+ how it works, what it controls, and exactly what the SDK sees and never
122
+ sees.
123
+ - **[Guides](docs/README.md#guides)** — keyed runs, fleet mode, spike
124
+ detection, action policy, self-hosted models, LangChain, async and
125
+ streaming.
126
+ - **[Reference](docs/README.md#reference)** — every configuration field, the
127
+ detectors, what happens when something trips, the circuit breaker, limits,
128
+ the compatibility matrix, privacy, and the guarantees.
129
+
130
+ [Roadmap](docs/roadmap.md) is what is shipped, what is still open, and what is
131
+ explicitly out of scope.
132
+
133
+ ## Getting help
134
+
135
+ **Something not working, or a question?** Open an issue at
136
+ [github.com/runboundai/runbound/issues](https://github.com/runboundai/runbound/issues),
137
+ or email **runboundai@gmail.com** if it is not something you can post in
138
+ public.
139
+
140
+ The two things that let us answer on the first reply instead of the third:
141
+
142
+ ```python
143
+ import runbound
144
+ print(runbound.__version__)
145
+ print(runbound.coverage()) # after init() and your first guarded call
146
+ ```
147
+
148
+ `coverage()` is plain numbers and names — which provider classes were
149
+ patched, how many calls and tools runbound saw, which imported providers
150
+ nothing is guarding, and any silent-zero warning — so it shows at a glance
151
+ whether runbound can see your traffic at all, which is the most common
152
+ cause of "it isn't catching anything". It lists your decorated tools'
153
+ names; remove any you would rather not post. Please also say your Python
154
+ version, your `openai` / `anthropic` versions, and whether you run with a
155
+ control plane (`token` / `control_plane_url`) or locally.
156
+
157
+ **Never paste** an API key, a control-plane token, a prompt, a reply, or a
158
+ real session key into an issue.
159
+
160
+ **Security issues go to email, not to an issue** — see
161
+ [SECURITY.md](SECURITY.md) for what to send and the disclosure window.
162
+
163
+ ## License
164
+
165
+ MIT — see [LICENSE](LICENSE). Release history is in
166
+ [CHANGELOG.md](CHANGELOG.md). The promises above, stated as bounds with the
167
+ tests that assert them (and where nothing does yet), are in
168
+ [INVARIANTS.md](INVARIANTS.md).
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runbound"
7
+ version = "0.3.0"
8
+ description = "Deterministic, LLM-free runaway detection and circuit breaking for AI agents."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Amanjot Singh" }]
13
+ keywords = [
14
+ "ai agents",
15
+ "observability",
16
+ "circuit breaker",
17
+ "runaway",
18
+ "cost control",
19
+ "llm",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Intended Audience :: Developers",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: System :: Monitoring",
30
+ ]
31
+ dependencies = []
32
+
33
+ [project.urls]
34
+ Homepage = "https://runbound.co"
35
+ Documentation = "https://runbound.co/docs"
36
+ Repository = "https://github.com/runboundai/runbound"
37
+ Changelog = "https://github.com/runboundai/runbound/blob/main/CHANGELOG.md"
38
+
39
+ [project.optional-dependencies]
40
+ test = ["pytest>=7.0"]
41
+ dev = ["pytest>=7.0"]
42
+ langchain = ["langchain-core"]
43
+
44
+ [tool.setuptools]
45
+ packages = ["runbound", "runbound.wrappers", "runbound.integrations"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
@@ -0,0 +1,89 @@
1
+ """runbound — deterministic, LLM-free runaway detection for AI agents.
2
+
3
+ Three lines to guard an agent::
4
+
5
+ import runbound
6
+
7
+ runbound.init(budget_usd=5.0, max_steps=50, on_anomaly="raise")
8
+ client = runbound.wrap(client) # OpenAI- or Anthropic-shaped
9
+
10
+ @runbound.tool # every tool call is recorded
11
+ def search(query): ...
12
+
13
+ Fail-open by design: runbound's own bugs are logged and swallowed, and the
14
+ only exception it raises on purpose is :class:`GuardrailTripped`.
15
+ """
16
+
17
+ from .alerts import verify_webhook_signature
18
+ from .api import (
19
+ active_sessions,
20
+ assert_guarded,
21
+ circuit_state,
22
+ clear,
23
+ coverage,
24
+ current_session,
25
+ fleet_status,
26
+ inflight_calls,
27
+ init,
28
+ is_tripped,
29
+ key_hash,
30
+ llm,
31
+ plane_status,
32
+ record_call,
33
+ reset,
34
+ session,
35
+ session_status,
36
+ tool,
37
+ tool_calls,
38
+ tools,
39
+ unpatch,
40
+ wrap,
41
+ )
42
+ from .config import GuardrailConfig
43
+ from .events import Anomaly, Event
44
+ from .exceptions import CircuitOpen, GuardrailTripped, PolicyViolation
45
+ from .plane_types import PlaneStatus
46
+ from .policy import ToolCall, ToolPolicy, Violation
47
+ from .responses import Refusal
48
+ from .state import SessionState
49
+
50
+ __version__ = "0.3.0"
51
+
52
+ __all__ = [
53
+ "Anomaly",
54
+ "CircuitOpen",
55
+ "Event",
56
+ "GuardrailConfig",
57
+ "GuardrailTripped",
58
+ "PlaneStatus",
59
+ "PolicyViolation",
60
+ "Refusal",
61
+ "SessionState",
62
+ "ToolCall",
63
+ "ToolPolicy",
64
+ "Violation",
65
+ "__version__",
66
+ "active_sessions",
67
+ "assert_guarded",
68
+ "circuit_state",
69
+ "clear",
70
+ "coverage",
71
+ "current_session",
72
+ "fleet_status",
73
+ "inflight_calls",
74
+ "init",
75
+ "is_tripped",
76
+ "key_hash",
77
+ "llm",
78
+ "plane_status",
79
+ "record_call",
80
+ "reset",
81
+ "session",
82
+ "session_status",
83
+ "tool",
84
+ "tool_calls",
85
+ "tools",
86
+ "unpatch",
87
+ "verify_webhook_signature",
88
+ "wrap",
89
+ ]