failproofai-sdk 0.0.1b1__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.
- failproofai_sdk-0.0.1b1/LICENSE +42 -0
- failproofai_sdk-0.0.1b1/MANIFEST.in +21 -0
- failproofai_sdk-0.0.1b1/PKG-INFO +548 -0
- failproofai_sdk-0.0.1b1/README.md +466 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/__init__.py +131 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_context.py +218 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_environment.py +78 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_events.py +752 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_resolver.py +118 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_runtime.py +21 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_schema.py +317 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_scopes.py +422 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_version.py +1 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/_writer.py +750 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/__init__.py +274 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/_compat.py +247 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/_core.py +1076 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/crewai.py +1669 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/langchain.py +2151 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/llama_index.py +1588 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk/py.typed +0 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk.egg-info/PKG-INFO +548 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk.egg-info/SOURCES.txt +75 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk.egg-info/dependency_links.txt +1 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk.egg-info/requires.txt +25 -0
- failproofai_sdk-0.0.1b1/failproofai_sdk.egg-info/top_level.txt +1 -0
- failproofai_sdk-0.0.1b1/pyproject.toml +92 -0
- failproofai_sdk-0.0.1b1/setup.cfg +4 -0
- failproofai_sdk-0.0.1b1/tests/__init__.py +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_context.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_docs.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_durability.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_encoding.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_integrations.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_no_customer_identifiers.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_packaging.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_resolver_umbrella.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_scopes.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_sdk.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_server_contract.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_site_docs.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_skill_snippets.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_spool_contract.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_spool_creation.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_wire_format.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/__pycache__/test_zero_dependencies.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/conftest.py +169 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__init__.py +6 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__pycache__/test_crewai.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__pycache__/test_langchain.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__pycache__/test_llama_index.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/integrations/__pycache__/test_pydantic_ai.cpython-312-pytest-9.1.1.pyc +0 -0
- failproofai_sdk-0.0.1b1/tests/integrations/test_crewai.py +1299 -0
- failproofai_sdk-0.0.1b1/tests/integrations/test_langchain.py +2338 -0
- failproofai_sdk-0.0.1b1/tests/integrations/test_llama_index.py +1576 -0
- failproofai_sdk-0.0.1b1/tests/integrations/test_pydantic_ai.py +1102 -0
- failproofai_sdk-0.0.1b1/tests/test_context.py +410 -0
- failproofai_sdk-0.0.1b1/tests/test_docs.py +411 -0
- failproofai_sdk-0.0.1b1/tests/test_durability.py +1352 -0
- failproofai_sdk-0.0.1b1/tests/test_encoding.py +571 -0
- failproofai_sdk-0.0.1b1/tests/test_integrations.py +1127 -0
- failproofai_sdk-0.0.1b1/tests/test_no_customer_identifiers.py +214 -0
- failproofai_sdk-0.0.1b1/tests/test_packaging.py +185 -0
- failproofai_sdk-0.0.1b1/tests/test_resolver_umbrella.py +178 -0
- failproofai_sdk-0.0.1b1/tests/test_scopes.py +487 -0
- failproofai_sdk-0.0.1b1/tests/test_sdk.py +923 -0
- failproofai_sdk-0.0.1b1/tests/test_server_contract.py +650 -0
- failproofai_sdk-0.0.1b1/tests/test_site_docs.py +371 -0
- failproofai_sdk-0.0.1b1/tests/test_skill_snippets.py +165 -0
- failproofai_sdk-0.0.1b1/tests/test_spool_contract.py +287 -0
- failproofai_sdk-0.0.1b1/tests/test_spool_creation.py +360 -0
- failproofai_sdk-0.0.1b1/tests/test_wire_format.py +279 -0
- failproofai_sdk-0.0.1b1/tests/test_zero_dependencies.py +378 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ExosphereHost Inc
|
|
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.
|
|
22
|
+
|
|
23
|
+
Commons Clause License Condition v1.0
|
|
24
|
+
|
|
25
|
+
The Software is provided to you by the Licensor under the License, as defined
|
|
26
|
+
below, subject to the following condition.
|
|
27
|
+
|
|
28
|
+
Without limiting other conditions in the License, the grant of rights under
|
|
29
|
+
the License will not include, and the License does not grant to you, the right
|
|
30
|
+
to Sell the Software.
|
|
31
|
+
|
|
32
|
+
For purposes of the foregoing, "Sell" means practicing any or all of the
|
|
33
|
+
rights granted to you under the License to provide to third parties, for a
|
|
34
|
+
fee or other consideration (including without limitation fees for hosting or
|
|
35
|
+
consulting/support services related to the Software), a product or service
|
|
36
|
+
whose value derives, entirely or substantially, from the functionality of the
|
|
37
|
+
Software. Any license notice or attribution required by the License must also
|
|
38
|
+
include this Commons Clause License Condition notice.
|
|
39
|
+
|
|
40
|
+
Software: failproofai
|
|
41
|
+
License: MIT
|
|
42
|
+
Licensor: ExosphereHost Inc
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The sdist ships `tests/`, and setuptools' default heuristic picks up only
|
|
2
|
+
# top-level `test*.py` — so the suite went out WITHOUT `tests/conftest.py`,
|
|
3
|
+
# `tests/__init__.py` or `tests/integrations/`.
|
|
4
|
+
#
|
|
5
|
+
# That is not a cosmetic omission. `conftest.py` IS the isolation layer: it
|
|
6
|
+
# redirects the spool at IMPORT, straight into `os.environ`, because the
|
|
7
|
+
# `atexit` flush fires long after the last fixture is torn down. Without it,
|
|
8
|
+
# running the shipped suite (a distro packager, a conda-forge recipe, anyone
|
|
9
|
+
# doing `pip download --no-binary :all:` then `pytest tests/`) writes tens of
|
|
10
|
+
# thousands of synthetic events into the user's REAL
|
|
11
|
+
# `~/.failproofai/custom-agents/events` — measured at 20,001 in one run of
|
|
12
|
+
# `test_durability.py` alone — where a running daemon uploads them to a real
|
|
13
|
+
# dashboard as genuine agent activity.
|
|
14
|
+
#
|
|
15
|
+
# So the suite ships whole or not at all. `graft` takes the whole tree,
|
|
16
|
+
# including the subpackage and the conftest.
|
|
17
|
+
graft tests
|
|
18
|
+
include LICENSE
|
|
19
|
+
include README.md
|
|
20
|
+
include py.typed
|
|
21
|
+
recursive-include failproofai_sdk py.typed
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: failproofai-sdk
|
|
3
|
+
Version: 0.0.1b1
|
|
4
|
+
Summary: Emit agent telemetry to FailproofAI Cloud — zero-dependency, stdlib only
|
|
5
|
+
Author-email: Failproof AI <failproofai@exosphere.host>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 ExosphereHost Inc
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Commons Clause License Condition v1.0
|
|
29
|
+
|
|
30
|
+
The Software is provided to you by the Licensor under the License, as defined
|
|
31
|
+
below, subject to the following condition.
|
|
32
|
+
|
|
33
|
+
Without limiting other conditions in the License, the grant of rights under
|
|
34
|
+
the License will not include, and the License does not grant to you, the right
|
|
35
|
+
to Sell the Software.
|
|
36
|
+
|
|
37
|
+
For purposes of the foregoing, "Sell" means practicing any or all of the
|
|
38
|
+
rights granted to you under the License to provide to third parties, for a
|
|
39
|
+
fee or other consideration (including without limitation fees for hosting or
|
|
40
|
+
consulting/support services related to the Software), a product or service
|
|
41
|
+
whose value derives, entirely or substantially, from the functionality of the
|
|
42
|
+
Software. Any license notice or attribution required by the License must also
|
|
43
|
+
include this Commons Clause License Condition notice.
|
|
44
|
+
|
|
45
|
+
Software: failproofai
|
|
46
|
+
License: MIT
|
|
47
|
+
Licensor: ExosphereHost Inc
|
|
48
|
+
|
|
49
|
+
Project-URL: Homepage, https://befailproof.ai
|
|
50
|
+
Project-URL: Documentation, https://docs.befailproof.ai/reference/custom-agents
|
|
51
|
+
Project-URL: Source, https://github.com/FailproofAI/failproofai
|
|
52
|
+
Project-URL: Changelog, https://github.com/FailproofAI/failproofai/blob/main/sdk/python/CHANGELOG.md
|
|
53
|
+
Keywords: agents,observability,ai,monitoring,failproofai,telemetry,sdk
|
|
54
|
+
Classifier: Development Status :: 4 - Beta
|
|
55
|
+
Classifier: Intended Audience :: Developers
|
|
56
|
+
Classifier: Programming Language :: Python :: 3
|
|
57
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
58
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
59
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
60
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
61
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
62
|
+
Classifier: Typing :: Typed
|
|
63
|
+
Requires-Python: >=3.10
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
License-File: LICENSE
|
|
66
|
+
Provides-Extra: dev
|
|
67
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
68
|
+
Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
|
|
69
|
+
Requires-Dist: pytest-asyncio<2,>=1.3; extra == "dev"
|
|
70
|
+
Provides-Extra: langchain
|
|
71
|
+
Requires-Dist: langchain-core<2,>=1.4.7; extra == "langchain"
|
|
72
|
+
Provides-Extra: langgraph
|
|
73
|
+
Requires-Dist: langgraph<2,>=1.2; extra == "langgraph"
|
|
74
|
+
Provides-Extra: crewai
|
|
75
|
+
Requires-Dist: crewai<2,>=1.13; extra == "crewai"
|
|
76
|
+
Requires-Dist: onnxruntime<1.24,>=1.14; python_version < "3.11" and extra == "crewai"
|
|
77
|
+
Provides-Extra: llamaindex
|
|
78
|
+
Requires-Dist: llama-index-core<0.15,>=0.14.23; extra == "llamaindex"
|
|
79
|
+
Provides-Extra: pydantic-ai
|
|
80
|
+
Requires-Dist: pydantic-ai-slim<3,>=2.0; extra == "pydantic-ai"
|
|
81
|
+
Dynamic: license-file
|
|
82
|
+
|
|
83
|
+
# failproofai-sdk
|
|
84
|
+
|
|
85
|
+
The Python SDK for [Failproof AI](https://befailproof.ai) agent observability. It
|
|
86
|
+
records what your agent did — tool calls, model requests, hooks, errors, waits for
|
|
87
|
+
a human — as structured events, and hands them to the daemon that ships them to
|
|
88
|
+
the platform.
|
|
89
|
+
|
|
90
|
+
- **PyPI distribution:** `failproofai-sdk`
|
|
91
|
+
- **Import name:** `failproofai_sdk`
|
|
92
|
+
- **Dependencies:** none. Standard library only, so installing it constrains
|
|
93
|
+
nothing else in your environment.
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install failproofai-sdk
|
|
99
|
+
# or
|
|
100
|
+
uv add failproofai-sdk
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **Do not `pip install agenteye`.** That distribution name is occupied on PyPI by
|
|
104
|
+
> a stranded build of an old CLI, which ships a module called `agenteye_cli` and is
|
|
105
|
+
> not this SDK. Installing it gives you `ModuleNotFoundError` at best, and — if
|
|
106
|
+
> this SDK is already present — pip treats it as an upgrade and **removes the SDK**
|
|
107
|
+
> to install the CLI. The import that worked five minutes ago then stops working.
|
|
108
|
+
|
|
109
|
+
## How events reach the platform
|
|
110
|
+
|
|
111
|
+
The SDK never opens a network connection. It appends events to an in-memory queue,
|
|
112
|
+
and a background thread writes them to local JSONL batches:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
your agent → failproofai_sdk → ~/.failproofai/custom-agents/events/*.jsonl → daemon → platform
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
A daemon on the same host watches that directory and uploads each batch.
|
|
119
|
+
**`failproofaid` reads this root; the older `agenteye-collector` does not** — it
|
|
120
|
+
resolves `$AGENTEYE_HOME` or `~/.agenteye` and nothing else, so a host running
|
|
121
|
+
only that collector needs one of the three bridges in the upgrade note below, or
|
|
122
|
+
its batches pile up unread. If no daemon is running, batches simply accumulate on
|
|
123
|
+
disk — the SDK does not fail, and your agent does not block.
|
|
124
|
+
|
|
125
|
+
## Agent frameworks
|
|
126
|
+
|
|
127
|
+
If your agent runs on LangChain/LangGraph, CrewAI, LlamaIndex or Pydantic AI,
|
|
128
|
+
one line captures it — runs, sub-agents, tools, model calls and their token
|
|
129
|
+
counts — without threading an id through anything:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import failproofai_sdk
|
|
133
|
+
|
|
134
|
+
failproofai_sdk.configure(environment="production")
|
|
135
|
+
failproofai_sdk.instrument() # auto-detects what is already imported
|
|
136
|
+
|
|
137
|
+
graph.invoke({"messages": [...]}) # unchanged
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install 'failproofai-sdk[langgraph]' # or [langchain] [crewai] [llamaindex] [pydantic-ai]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The adapter code ships in the base wheel and imports its framework lazily, so
|
|
145
|
+
the extras are a convenience — `pip install failproofai-sdk` still declares no
|
|
146
|
+
dependencies at all, and `import failproofai_sdk` loads nothing outside the
|
|
147
|
+
standard library. See `skill/references/frameworks.md` for the per-framework
|
|
148
|
+
mapping, and `docs/` for a per-framework integration guide with runnable
|
|
149
|
+
examples beside it.
|
|
150
|
+
|
|
151
|
+
## Scopes
|
|
152
|
+
|
|
153
|
+
The same identity layer, for code the adapters do not cover. `session_id` and
|
|
154
|
+
`agent_id` are optional on every event method — omitted, they resolve from the
|
|
155
|
+
enclosing scope:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
with failproofai_sdk.session() as sid:
|
|
159
|
+
with failproofai_sdk.agent("planner", goal=question):
|
|
160
|
+
|
|
161
|
+
with failproofai_sdk.tool_call("search", input={"q": q}) as t:
|
|
162
|
+
t.output = search(q) # tool_use / tool_result, timed
|
|
163
|
+
|
|
164
|
+
with failproofai_sdk.agent("writer"): # a sub-agent; parent inferred
|
|
165
|
+
failproofai_sdk.event.model_request(model="...")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`agent()` brackets a run with `agent_start`/`agent_end` and records an `error`
|
|
169
|
+
before the end event when the block raises — a cancellation closes it as
|
|
170
|
+
`cancelled` rather than failed. Every scope works under `async with` too.
|
|
171
|
+
|
|
172
|
+
Contextvars do **not** cross into a new thread, so hand work over with
|
|
173
|
+
`propagate`:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
pool.submit(failproofai_sdk.propagate(work), item)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Nothing bound and nothing passed raises `TypeError` naming the fix. It is never
|
|
180
|
+
a silent emit: ingest skips an event with no session and answers `200`.
|
|
181
|
+
|
|
182
|
+
## Quick start
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import failproofai_sdk
|
|
186
|
+
|
|
187
|
+
# Call once at startup. Omit to use defaults (~/.failproofai/custom-agents;
|
|
188
|
+
# 500ms flush interval).
|
|
189
|
+
failproofai_sdk.configure(base_dir=None, flush_interval=0.5)
|
|
190
|
+
|
|
191
|
+
# Emit events via failproofai_sdk.event.<method>(...)
|
|
192
|
+
failproofai_sdk.event.agent_start(session_id="run-001", agent_id="planner", goal="answer user query")
|
|
193
|
+
|
|
194
|
+
failproofai_sdk.event.tool_use(
|
|
195
|
+
session_id="run-001",
|
|
196
|
+
agent_id="planner",
|
|
197
|
+
tool_name="web_search",
|
|
198
|
+
tool_call_id="toolu_01",
|
|
199
|
+
input={"query": "latest AI research"},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
failproofai_sdk.event.tool_result(
|
|
203
|
+
session_id="run-001",
|
|
204
|
+
agent_id="planner",
|
|
205
|
+
tool_name="web_search",
|
|
206
|
+
tool_call_id="toolu_01", # matches tool_use — SDK auto-computes duration_ms
|
|
207
|
+
output={"results": ["..."]},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
failproofai_sdk.event.agent_end(session_id="run-001", agent_id="planner", outcome="success")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## configure()
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
failproofai_sdk.configure(
|
|
217
|
+
base_dir=None, # Path | str | None. Default:
|
|
218
|
+
# ~/.failproofai/custom-agents (honours $FAILPROOFAI_HOME)
|
|
219
|
+
flush_interval=0.5, # float, seconds between flush cycles
|
|
220
|
+
environment=None, # str | None. Else $AGENTEYE_ENVIRONMENT, else "dev"
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Call once before any `event.*` call. Safe to omit — defaults work out of the box.
|
|
225
|
+
When `base_dir` is `None`, the SDK spools to `~/.failproofai/custom-agents`
|
|
226
|
+
(honouring `$FAILPROOFAI_HOME`, which moves the umbrella; the `custom-agents`
|
|
227
|
+
segment is always appended, so the spool is always inside it).
|
|
228
|
+
|
|
229
|
+
**`base_dir` is the only way to spool anywhere else.** No environment variable
|
|
230
|
+
redirects it.
|
|
231
|
+
|
|
232
|
+
**The default spool root moved.** It was `~/.agenteye`. The daemon this SDK ships
|
|
233
|
+
beside, `failproofaid`, watches **both** roots and always has, so on a host
|
|
234
|
+
running it this changes which directory the files land in and nothing else.
|
|
235
|
+
Batches already sitting in `~/.agenteye/events` are not orphaned — they stay put
|
|
236
|
+
and are still collected; that directory simply stops growing.
|
|
237
|
+
|
|
238
|
+
> [!IMPORTANT]
|
|
239
|
+
> **If you run the older `agenteye-collector`, point IT at this SDK — not the
|
|
240
|
+
> other way round.** That collector resolves `$AGENTEYE_HOME` or `~/.agenteye`
|
|
241
|
+
> and nothing else, so it does not watch where this SDK writes: no upload, no
|
|
242
|
+
> error, and an unread spool looks exactly like an idle one.
|
|
243
|
+
>
|
|
244
|
+
> `AGENTEYE_HOME` used to be the way back, and is not any more — this SDK no
|
|
245
|
+
> longer reads it, so exporting it moves the collector and leaves the SDK where
|
|
246
|
+
> it was. Pick one of:
|
|
247
|
+
>
|
|
248
|
+
> * run `failproofaid` instead — it watches **both** roots, so nothing needs
|
|
249
|
+
> configuring; or
|
|
250
|
+
> * set **the collector's** `AGENTEYE_HOME=~/.failproofai/custom-agents`, so it
|
|
251
|
+
> watches `~/.failproofai/custom-agents/events` — where this SDK writes; or
|
|
252
|
+
> * `configure(base_dir="~/.agenteye")` in the application, which is explicit
|
|
253
|
+
> and visible at the call site.
|
|
254
|
+
|
|
255
|
+
`AGENTEYE_SPOOL_TO_FAILPROOFAI` is **retired**. It selected this root, but also
|
|
256
|
+
required the directory to already exist — and nothing ever created it, so the
|
|
257
|
+
opt-in never fired. Anyone who set it already wanted this and now gets it.
|
|
258
|
+
|
|
259
|
+
## Event reference
|
|
260
|
+
|
|
261
|
+
All event methods share two required fields:
|
|
262
|
+
|
|
263
|
+
| Field | Type | Description |
|
|
264
|
+
|-------|------|-------------|
|
|
265
|
+
| `session_id` | `str` | Identifies the top-level agent run |
|
|
266
|
+
| `agent_id` | `str` | Identifies which agent within the session emitted the event |
|
|
267
|
+
|
|
268
|
+
Every method also accepts arbitrary `**fields` for custom metadata (see [Custom fields](#custom-fields)).
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `event.tool_use()`
|
|
273
|
+
|
|
274
|
+
Emitted when an agent invokes a tool. Pair with `tool_result` — the SDK auto-computes `duration_ms`.
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
failproofai_sdk.event.tool_use(
|
|
278
|
+
session_id="run-001",
|
|
279
|
+
agent_id="planner",
|
|
280
|
+
tool_name="web_search", # str, required
|
|
281
|
+
tool_call_id="toolu_01", # str, required — correlation key for the matching tool_result
|
|
282
|
+
input={"query": "..."}, # dict | None
|
|
283
|
+
)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
### `event.tool_result()`
|
|
289
|
+
|
|
290
|
+
Emitted when a tool returns. Correlates with `tool_use` via `tool_call_id`.
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
failproofai_sdk.event.tool_result(
|
|
294
|
+
session_id="run-001",
|
|
295
|
+
agent_id="planner",
|
|
296
|
+
tool_name="web_search",
|
|
297
|
+
tool_call_id="toolu_01", # must match the prior tool_use
|
|
298
|
+
output={"results": ["..."]}, # Any | None
|
|
299
|
+
error=None, # str | None — set if the tool raised
|
|
300
|
+
# duration_ms is computed automatically — do not pass it
|
|
301
|
+
)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### `event.model_request()`
|
|
307
|
+
|
|
308
|
+
Emitted just before sending a prompt to an LLM.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
failproofai_sdk.event.model_request(
|
|
312
|
+
session_id="run-001",
|
|
313
|
+
agent_id="planner",
|
|
314
|
+
model="claude-opus-4-6", # str | None
|
|
315
|
+
messages=[ # list[dict] | None — conversation turns
|
|
316
|
+
{"role": "user", "content": "..."},
|
|
317
|
+
],
|
|
318
|
+
system="You are helpful.", # Any | None — str or list of content blocks
|
|
319
|
+
tools=[ # list[dict] | None — tool schemas offered to the model
|
|
320
|
+
{"name": "search", "input_schema": {"type": "object"}},
|
|
321
|
+
],
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`messages` entries accept either a plain string `content` or Anthropic-style list-of-blocks `content`. Sampling params (`temperature`, `max_tokens`, etc.) can be passed as extra kwargs.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `event.model_response()`
|
|
330
|
+
|
|
331
|
+
Emitted when the LLM returns a response.
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
failproofai_sdk.event.model_response(
|
|
335
|
+
session_id="run-001",
|
|
336
|
+
agent_id="planner",
|
|
337
|
+
model="claude-opus-4-6", # str | None
|
|
338
|
+
stop_reason="end_turn", # str | None
|
|
339
|
+
input_tokens=1024, # int | None
|
|
340
|
+
output_tokens=256, # int | None
|
|
341
|
+
content=[ # Any | None — str, or list of content blocks
|
|
342
|
+
{"type": "text", "text": "..."},
|
|
343
|
+
],
|
|
344
|
+
role="assistant", # str | None
|
|
345
|
+
)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`content` accepts either a plain string (generic providers) or a list of Anthropic-style content blocks. Tool calls live inside `content` as `{"type": "tool_use", ...}` blocks — no separate `tool_calls` field.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
### `event.agent_start()`
|
|
353
|
+
|
|
354
|
+
Emitted when an agent begins work.
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
failproofai_sdk.event.agent_start(
|
|
358
|
+
session_id="run-001",
|
|
359
|
+
agent_id="planner",
|
|
360
|
+
goal="answer user query", # str | None
|
|
361
|
+
parent_id=None, # str | None — parent agent_id for nested agents
|
|
362
|
+
)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### `event.agent_end()`
|
|
368
|
+
|
|
369
|
+
Emitted when an agent finishes work.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
failproofai_sdk.event.agent_end(
|
|
373
|
+
session_id="run-001",
|
|
374
|
+
agent_id="planner",
|
|
375
|
+
outcome="success", # str | None
|
|
376
|
+
summary="Answered query", # str | None
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### `event.agent_pause()`
|
|
383
|
+
|
|
384
|
+
Emitted when an agent is suspended (e.g. waiting for human input, user-requested
|
|
385
|
+
pause, throttling). Does **not** end the agent — pair it with `agent_resume`, and
|
|
386
|
+
the SDK auto-computes the paused `duration_ms`. Emit `agent_resume` instead of a
|
|
387
|
+
second `agent_start` when the agent continues.
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
failproofai_sdk.event.agent_pause(
|
|
391
|
+
session_id="run-001",
|
|
392
|
+
agent_id="planner",
|
|
393
|
+
pause_id="pause-abc", # str, required — correlation key (reuse it on agent_resume)
|
|
394
|
+
reason="waiting_for_user", # str | None
|
|
395
|
+
user_id="usr_42", # str | None — who paused, if user-initiated
|
|
396
|
+
)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`pause_id` is emitted on both events, so a pause always pairs to its resume — even
|
|
400
|
+
when they happen in different processes (in that case `duration_ms` is omitted and
|
|
401
|
+
the interval is derived downstream from the two timestamps).
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### `event.agent_resume()`
|
|
406
|
+
|
|
407
|
+
Emitted when a paused agent continues. Correlates with `agent_pause` via `pause_id`;
|
|
408
|
+
the SDK auto-computes `duration_ms` (how long the agent was paused).
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
failproofai_sdk.event.agent_resume(
|
|
412
|
+
session_id="run-001",
|
|
413
|
+
agent_id="planner",
|
|
414
|
+
pause_id="pause-abc", # str, required — must match the prior agent_pause
|
|
415
|
+
reason="user_resumed", # str | None
|
|
416
|
+
user_id="usr_42", # str | None
|
|
417
|
+
# duration_ms is computed automatically — do not pass it
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### `event.hook_triggered()`
|
|
424
|
+
|
|
425
|
+
Emitted when a hook fires. Pair with `hook_completed` — the SDK auto-computes `duration_ms`.
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
failproofai_sdk.event.hook_triggered(
|
|
429
|
+
session_id="run-001",
|
|
430
|
+
agent_id="planner",
|
|
431
|
+
hook_name="pre_tool_use", # str, required
|
|
432
|
+
hook_id="hook-abc", # str, required — correlation key
|
|
433
|
+
trigger_event="tool_use", # str | None
|
|
434
|
+
input={"tool": "search"}, # Any | None
|
|
435
|
+
)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
### `event.hook_completed()`
|
|
441
|
+
|
|
442
|
+
Emitted when a hook finishes. Correlates with `hook_triggered` via `hook_id`.
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
failproofai_sdk.event.hook_completed(
|
|
446
|
+
session_id="run-001",
|
|
447
|
+
agent_id="planner",
|
|
448
|
+
hook_name="pre_tool_use",
|
|
449
|
+
hook_id="hook-abc", # must match the prior hook_triggered
|
|
450
|
+
outcome="allow", # str | None
|
|
451
|
+
output=None, # Any | None
|
|
452
|
+
error=None, # str | None
|
|
453
|
+
# duration_ms is computed automatically — do not pass it
|
|
454
|
+
)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### `event.error()`
|
|
460
|
+
|
|
461
|
+
Emitted when an unhandled error occurs.
|
|
462
|
+
|
|
463
|
+
```python
|
|
464
|
+
failproofai_sdk.event.error(
|
|
465
|
+
session_id="run-001",
|
|
466
|
+
agent_id="planner",
|
|
467
|
+
error_type="TimeoutError", # str, required
|
|
468
|
+
message="timed out", # str, required
|
|
469
|
+
traceback="Traceback...", # str | None
|
|
470
|
+
)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Custom fields
|
|
476
|
+
|
|
477
|
+
Any extra keyword arguments are appended to the event after the standard fields:
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
failproofai_sdk.event.tool_use(
|
|
481
|
+
session_id="run-001",
|
|
482
|
+
agent_id="planner",
|
|
483
|
+
tool_name="db_query",
|
|
484
|
+
tool_call_id="toolu_02",
|
|
485
|
+
request_id="req-123", # custom field
|
|
486
|
+
tenant_id="acme", # custom field
|
|
487
|
+
)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
The field names `timestamp`, `type`, and `environment` are reserved and raise
|
|
491
|
+
`ValueError` if passed as custom fields. `session_id` and `agent_id` are required
|
|
492
|
+
parameters and cannot be supplied a second time. Set the environment with
|
|
493
|
+
`configure(environment=...)` or `AGENTEYE_ENVIRONMENT`.
|
|
494
|
+
|
|
495
|
+
Keep payloads as structured JSON when downstream queries need their fields. Values
|
|
496
|
+
JSON does not natively support—such as datetimes, UUIDs, decimals, sets, bytes, or
|
|
497
|
+
model objects—are converted to strings so the writer can continue flushing the batch.
|
|
498
|
+
|
|
499
|
+
## JSONL output
|
|
500
|
+
|
|
501
|
+
Events are buffered in-process and flushed to disk every `flush_interval` seconds (default 500ms).
|
|
502
|
+
Each flush writes one JSONL file:
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
~/.failproofai/custom-agents/events/event-2026-04-01T12-00-00-000Z-48213-7.jsonl
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Each line is one JSON object. Example:
|
|
509
|
+
|
|
510
|
+
```json
|
|
511
|
+
{"timestamp": "2026-04-01T12:00:00.000000Z", "session_id": "run-001", "agent_id": "planner", "type": "agent_start", "goal": "answer user query"}
|
|
512
|
+
{"timestamp": "2026-04-01T12:00:00.123456Z", "session_id": "run-001", "agent_id": "planner", "type": "tool_use", "tool_name": "web_search", "tool_call_id": "toolu_01"}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
The batch is published by writing a `.tmp` file and atomically renaming it to
|
|
516
|
+
`.jsonl`, so a daemon polling the directory never reads a half-written file. The
|
|
517
|
+
trailing `<pid>-<seq>` keeps two batches written in the same millisecond — by two
|
|
518
|
+
threads, or by two agent processes sharing the spool — from overwriting each
|
|
519
|
+
other. You do not need to manage these files directly.
|
|
520
|
+
|
|
521
|
+
## Development
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
# Install dev dependencies
|
|
525
|
+
uv sync --locked --extra dev
|
|
526
|
+
|
|
527
|
+
# Run the test suite
|
|
528
|
+
uv run pytest tests/ -v
|
|
529
|
+
|
|
530
|
+
# Run a single test
|
|
531
|
+
uv run pytest tests/test_sdk.py -k duration -v
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
| Suite | What it holds |
|
|
535
|
+
|---|---|
|
|
536
|
+
| `test_sdk.py` | The public API — every event method, unit and on-disk |
|
|
537
|
+
| `test_wire_format.py` | Golden bytes for all 15 event types, frozen |
|
|
538
|
+
| `test_server_contract.py` | The keys ingest promotes to indexed columns |
|
|
539
|
+
| `test_spool_contract.py` | Agreement with the daemons that read the spool |
|
|
540
|
+
| `test_durability.py` | Concurrency, crash and retry paths — nothing silently lost |
|
|
541
|
+
| `test_zero_dependencies.py` | The stdlib-only guarantee |
|
|
542
|
+
| `test_no_customer_identifiers.py` | Nothing private ships in a public wheel |
|
|
543
|
+
|
|
544
|
+
Two suites reach for sources outside this package. `test_spool_contract.py` reads
|
|
545
|
+
the Rust and TypeScript in this repo and never skips; set
|
|
546
|
+
`FAILPROOFAI_SDK_REQUIRE_CONTRACT=1` (CI does) so a moved file fails instead of
|
|
547
|
+
skipping. Set `FP_AGENTEYE_ROOT` to an AgentEye checkout to additionally verify
|
|
548
|
+
against the older collector and the live ingest handler.
|