agentsonar 0.1.2__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.
- agentsonar-0.1.2/.gitignore +16 -0
- agentsonar-0.1.2/PKG-INFO +463 -0
- agentsonar-0.1.2/README.md +431 -0
- agentsonar-0.1.2/docs/images/htmlreport.png +0 -0
- agentsonar-0.1.2/examples/crewai_clean_sequential.py +168 -0
- agentsonar-0.1.2/examples/crewai_delegation_ping_pong.py +265 -0
- agentsonar-0.1.2/examples/crewai_hello_world.py +138 -0
- agentsonar-0.1.2/examples/engine_only.py +121 -0
- agentsonar-0.1.2/examples/langgraph_clean_pipeline.py +86 -0
- agentsonar-0.1.2/examples/langgraph_cycle_and_repetitive.py +360 -0
- agentsonar-0.1.2/examples/langgraph_minimal.py +127 -0
- agentsonar-0.1.2/examples/langgraph_minimal_combo.py +126 -0
- agentsonar-0.1.2/examples/langgraph_minimal_rate_limit.py +86 -0
- agentsonar-0.1.2/examples/langgraph_reviewer_insufficient_loop.py +169 -0
- agentsonar-0.1.2/examples/warning_and_critical.py +135 -0
- agentsonar-0.1.2/pyproject.toml +114 -0
- agentsonar-0.1.2/src/agentsonar/__init__.py +52 -0
- agentsonar-0.1.2/src/agentsonar/_core/__init__.py +6 -0
- agentsonar-0.1.2/src/agentsonar/_core/constants.py +16 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/__init__.py +22 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/alert_state.py +202 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/cycle_detector.py +81 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/rate_limiter.py +297 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/repetitive_detector.py +171 -0
- agentsonar-0.1.2/src/agentsonar/_core/detectors/scc_analyzer.py +83 -0
- agentsonar-0.1.2/src/agentsonar/_core/engine.py +960 -0
- agentsonar-0.1.2/src/agentsonar/_core/graph.py +47 -0
- agentsonar-0.1.2/src/agentsonar/_core/models.py +35 -0
- agentsonar-0.1.2/src/agentsonar/_core/noop_engine.py +145 -0
- agentsonar-0.1.2/src/agentsonar/_core/schema.py +565 -0
- agentsonar-0.1.2/src/agentsonar/_integrations/__init__.py +8 -0
- agentsonar-0.1.2/src/agentsonar/_integrations/_safe_engine.py +228 -0
- agentsonar-0.1.2/src/agentsonar/_integrations/crewai_listener.py +217 -0
- agentsonar-0.1.2/src/agentsonar/_integrations/langgraph_callback.py +431 -0
- agentsonar-0.1.2/src/agentsonar/_output/__init__.py +16 -0
- agentsonar-0.1.2/src/agentsonar/_output/_slug.py +127 -0
- agentsonar-0.1.2/src/agentsonar/_output/html_report.py +919 -0
- agentsonar-0.1.2/src/agentsonar/_output/json_export.py +161 -0
- agentsonar-0.1.2/src/agentsonar/_output/terminal.py +1052 -0
- agentsonar-0.1.2/tests/__init__.py +0 -0
- agentsonar-0.1.2/tests/test_clean_run_signals.py +170 -0
- agentsonar-0.1.2/tests/test_cross_platform.py +426 -0
- agentsonar-0.1.2/tests/test_detector.py +911 -0
- agentsonar-0.1.2/tests/test_exports.py +923 -0
- agentsonar-0.1.2/tests/test_host_safety.py +343 -0
- agentsonar-0.1.2/tests/test_langgraph.py +466 -0
- agentsonar-0.1.2/tests/test_logger.py +1341 -0
- agentsonar-0.1.2/tests/test_mid_execution_safety.py +499 -0
- agentsonar-0.1.2/tests/test_patterns.py +609 -0
- agentsonar-0.1.2/tests/test_performance.py +298 -0
- agentsonar-0.1.2/tests/test_public_api.py +301 -0
- agentsonar-0.1.2/tests/test_round2_fixes.py +665 -0
- agentsonar-0.1.2/tests/test_round3_fixes.py +522 -0
- agentsonar-0.1.2/tests/test_round4_fixes.py +321 -0
- agentsonar-0.1.2/tests/test_schema.py +927 -0
- agentsonar-0.1.2/tests/test_slug.py +161 -0
- agentsonar-0.1.2/tests/test_stress_and_edge_cases.py +608 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# AgentSonar runtime output — JSONL logs and HTML/JSON reports produced
|
|
13
|
+
# when running examples or user code in the repo root.
|
|
14
|
+
agentsonar_*.log
|
|
15
|
+
agentsonar_*.json
|
|
16
|
+
agentsonar_*.html
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentsonar
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Detect coordination failures in multi-agent AI systems (CrewAI, LangGraph, and more)
|
|
5
|
+
Author: AgentSonar
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: agents,ai,crewai,detection,langgraph,monitoring,multi-agent,observability
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: System :: Monitoring
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: networkx>=3.0
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: crewai>=0.70.0; extra == 'all'
|
|
24
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'all'
|
|
25
|
+
Requires-Dist: langgraph>=0.2.0; extra == 'all'
|
|
26
|
+
Provides-Extra: crewai
|
|
27
|
+
Requires-Dist: crewai>=0.70.0; extra == 'crewai'
|
|
28
|
+
Provides-Extra: langgraph
|
|
29
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'langgraph'
|
|
30
|
+
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# AgentSonar
|
|
34
|
+
|
|
35
|
+
AgentSonar detects coordination failures in multi-agent AI systems —
|
|
36
|
+
cycles, repetitive delegation, and runaway throughput — before they
|
|
37
|
+
burn through your token budget. **Two lines of code to integrate.**
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install agentsonar[crewai] # for CrewAI
|
|
43
|
+
pip install agentsonar[langgraph] # for LangGraph / LangChain
|
|
44
|
+
pip install agentsonar[all] # both
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Framework integrations are optional extras — install only what you need.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### CrewAI
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from agentsonar import AgentSonarListener
|
|
55
|
+
|
|
56
|
+
sonar = AgentSonarListener()
|
|
57
|
+
# ...run your crew normally. Detection happens automatically.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
→ Full runnable example: [CrewAI minimal setup](#crewai-minimal-setup).
|
|
61
|
+
|
|
62
|
+
### LangGraph / LangChain
|
|
63
|
+
|
|
64
|
+
Two equivalent patterns — pick whichever fits your existing code better.
|
|
65
|
+
|
|
66
|
+
**Pattern 1: `monitor()` wrapper (recommended if you already have callbacks)**
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agentsonar import monitor
|
|
70
|
+
|
|
71
|
+
graph = monitor(graph)
|
|
72
|
+
result = graph.invoke(input)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`monitor()` wraps the compiled graph so every `invoke`/`stream`/`ainvoke`/
|
|
76
|
+
`astream` call auto-injects AgentSonar's callback into your config — without
|
|
77
|
+
overriding any callbacks you pass. If you already have your own callbacks:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
graph = monitor(graph)
|
|
81
|
+
result = graph.invoke(input, config={"callbacks": [my_cb]})
|
|
82
|
+
# Both callbacks run: [my_cb, AgentSonarCallback()]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Pattern 2: direct callback injection**
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from agentsonar import AgentSonarCallback
|
|
89
|
+
|
|
90
|
+
result = graph.invoke(
|
|
91
|
+
input,
|
|
92
|
+
config={"callbacks": [AgentSonarCallback()]},
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use this pattern when you want explicit control over callback order. You're
|
|
97
|
+
responsible for merging AgentSonar with any existing callbacks yourself —
|
|
98
|
+
the `monitor()` wrapper handles that automatically.
|
|
99
|
+
|
|
100
|
+
→ Full runnable example: [LangGraph minimal setup](#langgraph-minimal-setup).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
That's the whole API. Zero config required to get started — no API keys,
|
|
105
|
+
no accounts. Alerts stream to stderr as they fire and land in per-run
|
|
106
|
+
log files on disk. See [Output](#output) for everything AgentSonar writes
|
|
107
|
+
and [Configuration](#configuration) for the knobs you can tune.
|
|
108
|
+
|
|
109
|
+
<a id="output"></a>
|
|
110
|
+
## Output
|
|
111
|
+
|
|
112
|
+
Every run creates its **own session directory** under `agentsonar_logs/`
|
|
113
|
+
in your working directory. All artifacts for a single run live together:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
your_project/
|
|
117
|
+
└── agentsonar_logs/
|
|
118
|
+
├── .gitignore # auto-written, contains *
|
|
119
|
+
├── latest # plain text: current run dir name
|
|
120
|
+
├── run-2026-04-11_07-03-23-ancient-ember/ # most recent run
|
|
121
|
+
│ ├── timeline.jsonl # every event, JSONL
|
|
122
|
+
│ ├── alerts.log # human-readable signal-only
|
|
123
|
+
│ ├── report.json # structured summary report
|
|
124
|
+
│ └── report.html # standalone HTML report
|
|
125
|
+
└── run-2026-04-11_07-03-13-quiet-blossom/ # previous run
|
|
126
|
+
└── ...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Live alerts also stream to **stderr** as they fire, prefixed with
|
|
130
|
+
`[SONAR HH:MM:SS.mmm]`.
|
|
131
|
+
|
|
132
|
+
### Calling shutdown()
|
|
133
|
+
|
|
134
|
+
The JSON and HTML reports are generated on `shutdown()` — so **how**
|
|
135
|
+
you end your run determines whether those two files get written.
|
|
136
|
+
|
|
137
|
+
**LangGraph / LangChain** — call `shutdown()` yourself when the run
|
|
138
|
+
completes. Both wrapper patterns expose it:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
# Pattern 1: monitor() wrapper — shutdown lives on the wrapper
|
|
142
|
+
graph = monitor(graph)
|
|
143
|
+
graph.invoke(input)
|
|
144
|
+
graph.shutdown() # ← writes report.json + report.html, closes log files
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Pattern 2: direct AgentSonarCallback — shutdown lives on the callback
|
|
149
|
+
sonar = AgentSonarCallback()
|
|
150
|
+
graph.invoke(input, config={"callbacks": [sonar]})
|
|
151
|
+
sonar.shutdown() # ← writes report.json + report.html, closes log files
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If you forget to call it, `timeline.jsonl` and the stderr stream are
|
|
155
|
+
still captured (they flush event-by-event), but the structured
|
|
156
|
+
`report.json` / `report.html` summary files won't be generated for that run.
|
|
157
|
+
|
|
158
|
+
**CrewAI** — no teardown needed. `AgentSonarListener` hooks
|
|
159
|
+
`CrewKickoffCompletedEvent` on the CrewAI event bus and runs
|
|
160
|
+
`shutdown()` automatically when `crew.kickoff()` finishes. You get the
|
|
161
|
+
full output set — including `report.json` and `report.html` — without
|
|
162
|
+
any extra code.
|
|
163
|
+
|
|
164
|
+
### What `report.html` looks like
|
|
165
|
+
|
|
166
|
+
The standalone HTML report (`report.html`) is a self-contained page —
|
|
167
|
+
no external CSS or JavaScript, no network requests, safe to email,
|
|
168
|
+
archive, or commit as a debugging artifact. Each coordination event
|
|
169
|
+
renders as a card with its severity, failure class (with hover
|
|
170
|
+
tooltip), summary, fingerprint, and expandable topology / thresholds
|
|
171
|
+
blocks. Dark mode respects your system preference and persists
|
|
172
|
+
across runs. Open it with your browser — or forward to a colleague
|
|
173
|
+
and they'll see the same interactive view without any install steps.
|
|
174
|
+
|
|
175
|
+
### Realtime tailing
|
|
176
|
+
|
|
177
|
+
`timeline.jsonl` is flushed on every event, so you can watch coordination
|
|
178
|
+
problems land as they happen — useful when a long-running crew is behaving
|
|
179
|
+
oddly and you want to catch the exact delegation that triggered an alert:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Unix / macOS — tail the current run's timeline
|
|
183
|
+
tail -f "agentsonar_logs/$(cat agentsonar_logs/latest)/timeline.jsonl"
|
|
184
|
+
|
|
185
|
+
# Only the signal-only (alerts) view
|
|
186
|
+
tail -f "agentsonar_logs/$(cat agentsonar_logs/latest)/alerts.log"
|
|
187
|
+
```
|
|
188
|
+
```powershell
|
|
189
|
+
# Windows PowerShell — -Wait is the tail-follow equivalent
|
|
190
|
+
Get-Content "agentsonar_logs/$(Get-Content agentsonar_logs/latest)/timeline.jsonl" -Wait
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Each JSONL line is a self-contained record with `ts`, `level`, `event`,
|
|
194
|
+
and a `data` payload — easy to pipe through `jq` or a custom parser if
|
|
195
|
+
you want realtime dashboards during a run.
|
|
196
|
+
|
|
197
|
+
<a id="clean-run-signals"></a>
|
|
198
|
+
### Clean-run signals
|
|
199
|
+
|
|
200
|
+
When the run completes without any WARNING or CRITICAL alerts, AgentSonar
|
|
201
|
+
tells you explicitly in every channel instead of leaving you to wonder:
|
|
202
|
+
|
|
203
|
+
- **HTML report** — a green "✓ No coordination failures detected" banner
|
|
204
|
+
replaces the event cards.
|
|
205
|
+
- **`timeline.jsonl`** — the final `session_end` record carries
|
|
206
|
+
`"clean_run": true`, `"warning_count": 0`, `"critical_count": 0`, and
|
|
207
|
+
a plain-English `message` field. Downstream parsers can gate on the
|
|
208
|
+
single boolean instead of walking the whole alerts list.
|
|
209
|
+
- **stderr** — a green `✓ No coordination failures detected — clean run.`
|
|
210
|
+
line prints right under the summary banner.
|
|
211
|
+
|
|
212
|
+
Non-clean runs flip `clean_run` to `false` and the `message` includes
|
|
213
|
+
the severity breakdown (e.g. `"2 CRITICAL, 5 WARNING coordination alert(s)
|
|
214
|
+
detected during this run."`).
|
|
215
|
+
|
|
216
|
+
### Run naming, latest pointer, retention
|
|
217
|
+
|
|
218
|
+
Run directories are named `run-<ISO date>_<time>-<adjective>-<noun>`
|
|
219
|
+
(e.g. `run-2026-04-11_07-03-23-ancient-ember`). The timestamp sorts
|
|
220
|
+
chronologically; the slug is memorable enough to say out loud and is
|
|
221
|
+
deterministic from the session id.
|
|
222
|
+
|
|
223
|
+
`agentsonar_logs/latest` is a plain text file pointing at the newest
|
|
224
|
+
run directory name. Open the newest HTML report with:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# Unix / macOS
|
|
228
|
+
open "agentsonar_logs/$(cat agentsonar_logs/latest)/report.html"
|
|
229
|
+
```
|
|
230
|
+
```powershell
|
|
231
|
+
# Windows PowerShell
|
|
232
|
+
Invoke-Item "agentsonar_logs/$(Get-Content agentsonar_logs/latest)/report.html"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
AgentSonar keeps the **20 most recent runs** by default and prunes
|
|
236
|
+
older ones on every new session. Configure via `AGENTSONAR_KEEP_RUNS`
|
|
237
|
+
or `config={"keep_runs": N}`; set to `0` to disable pruning.
|
|
238
|
+
|
|
239
|
+
The auto-written `.gitignore` inside `agentsonar_logs/` contains `*`,
|
|
240
|
+
so every log and report is git-ignored by default.
|
|
241
|
+
|
|
242
|
+
### Opt-out
|
|
243
|
+
|
|
244
|
+
If you don't want the JSON/HTML reports, disable them in the config:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
sonar = AgentSonarCallback(config={"auto_export_on_shutdown": False})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Manual export
|
|
251
|
+
|
|
252
|
+
If you want to export at multiple checkpoints during a run, or want the
|
|
253
|
+
full timeline view (`dedupe=False`), call the exporters explicitly:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from agentsonar._output.json_export import export_json
|
|
257
|
+
from agentsonar._output.html_report import export_html
|
|
258
|
+
|
|
259
|
+
events = sonar.engine.get_recent_events()
|
|
260
|
+
|
|
261
|
+
# Summary view (default): one line per root cause
|
|
262
|
+
export_json(events)
|
|
263
|
+
export_html(events, title="Checkpoint 1")
|
|
264
|
+
|
|
265
|
+
# Timeline view: every state transition preserved
|
|
266
|
+
export_json(events, dedupe=False)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
<a id="configuration"></a>
|
|
270
|
+
<details>
|
|
271
|
+
<summary><strong>Configuration reference</strong></summary>
|
|
272
|
+
|
|
273
|
+
Every entry point — `monitor()`, `AgentSonarCallback()`, and
|
|
274
|
+
`AgentSonarListener()` — accepts an optional `config` dict to override
|
|
275
|
+
the defaults. Pass only the keys you want to change; everything else
|
|
276
|
+
keeps its default.
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
graph = monitor(graph, config={
|
|
280
|
+
# Rate limits — raise these when you're running at demo speeds
|
|
281
|
+
# (much faster than any real LLM workload) and don't want the
|
|
282
|
+
# circuit breaker to trip before detection has a chance to see
|
|
283
|
+
# the full pattern.
|
|
284
|
+
"per_edge_limit": 99999, # default 10 events per edge per window
|
|
285
|
+
"global_limit": 99999, # default 200 events total per window
|
|
286
|
+
"window_size": 180.0, # default 180.0 seconds
|
|
287
|
+
|
|
288
|
+
# Alert severity thresholds — how many rotations/events before
|
|
289
|
+
# WARNING and CRITICAL fire. Lower them for tight tests, raise
|
|
290
|
+
# them for noisy production workloads.
|
|
291
|
+
"warning_threshold": 5, # default 5
|
|
292
|
+
"critical_threshold": 15, # default 15
|
|
293
|
+
|
|
294
|
+
# Output location and retention
|
|
295
|
+
"log_dir": ".", # parent directory for agentsonar_logs/
|
|
296
|
+
"keep_runs": 20, # most recent run dirs to keep (0 = no pruning)
|
|
297
|
+
"console_output": True, # stream colored alerts to stderr
|
|
298
|
+
"file_output": True, # write timeline.jsonl + alerts.log
|
|
299
|
+
"auto_export_on_shutdown": True, # write report.json + report.html
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The same dict works for every entry point:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
AgentSonarCallback(config={"per_edge_limit": 99999})
|
|
307
|
+
AgentSonarListener(config={"warning_threshold": 3})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The rate-limit knobs are the most common override. AgentSonar's default
|
|
311
|
+
circuit breaker is tuned for real LLM workloads (a few hundred ms per
|
|
312
|
+
call); scripted demos and unit tests fire events orders of magnitude
|
|
313
|
+
faster, which trips the breaker before the downstream cycle/repetition
|
|
314
|
+
detectors get a chance to fire. Bumping `per_edge_limit` and
|
|
315
|
+
`global_limit` to a large value disables that short-circuit for local
|
|
316
|
+
runs.
|
|
317
|
+
|
|
318
|
+
</details>
|
|
319
|
+
|
|
320
|
+
## What gets detected
|
|
321
|
+
|
|
322
|
+
Every detected event carries a `failure_class` string that names the kind
|
|
323
|
+
of problem. The classes AgentSonar currently surfaces:
|
|
324
|
+
|
|
325
|
+
- **`cyclic_delegation`** — Agents are stuck in a loop. A delegates to B,
|
|
326
|
+
B to C, C back to A. Usually means an exit condition is missing — a
|
|
327
|
+
reviewer that never approves, a planner that always says "revise".
|
|
328
|
+
|
|
329
|
+
- **`repetitive_delegation`** — One agent keeps calling another without
|
|
330
|
+
making progress. `A → B` fires many times in a short window with no
|
|
331
|
+
`B → A` return. Usually means A can't make a decision without B and
|
|
332
|
+
isn't getting what it needs.
|
|
333
|
+
|
|
334
|
+
- **`resource_exhaustion`** — The system is processing events faster
|
|
335
|
+
than it can sustain. Either one edge is being hammered or total
|
|
336
|
+
throughput is over budget. Indicates a runaway agent or throttling
|
|
337
|
+
that's too loose.
|
|
338
|
+
|
|
339
|
+
**Coming soon** (reserved class names; no-op today):
|
|
340
|
+
`cascade_failure`, `authority_violation`, `deadlock`, `agent_stall`,
|
|
341
|
+
`token_velocity_anomaly`.
|
|
342
|
+
|
|
343
|
+
The HTML report shows a hover tooltip on every failure class badge with
|
|
344
|
+
the same plain-English description.
|
|
345
|
+
|
|
346
|
+
## Coordination fingerprint
|
|
347
|
+
|
|
348
|
+
Every detected failure carries a `coordination_fingerprint` like
|
|
349
|
+
`sha256:5c102a66e1104c47`. It's a stable ID for the failure pattern:
|
|
350
|
+
the same failure (same agents in the same shape) always produces the
|
|
351
|
+
same fingerprint, regardless of when it's detected or how many times
|
|
352
|
+
it re-escalates. You use it for:
|
|
353
|
+
|
|
354
|
+
- **Dedup.** `WARNING → CRITICAL` escalations share a fingerprint, so
|
|
355
|
+
the summary view collapses to one row at the highest severity.
|
|
356
|
+
- **Root-cause grouping.** When a cycle is firing, the repetitive-edge
|
|
357
|
+
alerts on each cycle edge are suppressed in the summary view.
|
|
358
|
+
- **Cross-run correlation.** Grep any log file for the fingerprint to
|
|
359
|
+
find every time the same pattern fired, across sessions.
|
|
360
|
+
|
|
361
|
+
You never compute fingerprints yourself — they're just stable IDs
|
|
362
|
+
you can use to talk about "the same failure" across time.
|
|
363
|
+
|
|
364
|
+
## Host safety
|
|
365
|
+
|
|
366
|
+
AgentSonar never crashes your app. If anything inside the SDK fails,
|
|
367
|
+
detection degrades silently to a no-op and your crew / graph / API
|
|
368
|
+
keeps running. The kill switch is `AGENTSONAR_DISABLED=1` (also accepts
|
|
369
|
+
`true`, `yes`, `on`, `enabled`, case-insensitive) — set it in the
|
|
370
|
+
environment to disable AgentSonar without editing code. When running
|
|
371
|
+
in degraded mode, `get_summary()` returns `{"degraded": True, ...}`
|
|
372
|
+
so you can alert on it from your own dashboards.
|
|
373
|
+
|
|
374
|
+
## Full minimal setup
|
|
375
|
+
|
|
376
|
+
Copy-pasteable starting points. The AgentSonar lines are marked — the
|
|
377
|
+
rest is plain framework code. Both examples are self-contained and
|
|
378
|
+
runnable as-is.
|
|
379
|
+
|
|
380
|
+
<a id="langgraph-minimal-setup"></a>
|
|
381
|
+
### LangGraph minimal setup
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
# pip install agentsonar[langgraph]
|
|
385
|
+
import operator
|
|
386
|
+
from typing import Literal
|
|
387
|
+
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
|
|
388
|
+
from langgraph.graph import END, START, StateGraph
|
|
389
|
+
from typing_extensions import Annotated, TypedDict
|
|
390
|
+
|
|
391
|
+
from agentsonar import monitor # ← AgentSonar
|
|
392
|
+
|
|
393
|
+
class State(TypedDict):
|
|
394
|
+
messages: Annotated[list[AnyMessage], operator.add]
|
|
395
|
+
iteration: int
|
|
396
|
+
|
|
397
|
+
def planner(s): return {"messages": [AIMessage(content="plan")]}
|
|
398
|
+
def researcher(s): return {"messages": [AIMessage(content="research")]}
|
|
399
|
+
def reviewer(s): return {"messages": [AIMessage(content="revise")],
|
|
400
|
+
"iteration": s.get("iteration", 0) + 1}
|
|
401
|
+
def loop(s) -> Literal["planner", "__end__"]:
|
|
402
|
+
return END if s.get("iteration", 0) >= 8 else "planner"
|
|
403
|
+
|
|
404
|
+
b = StateGraph(State)
|
|
405
|
+
for name, fn in [("planner", planner), ("researcher", researcher), ("reviewer", reviewer)]:
|
|
406
|
+
b.add_node(name, fn)
|
|
407
|
+
b.add_edge(START, "planner")
|
|
408
|
+
b.add_edge("planner", "researcher")
|
|
409
|
+
b.add_edge("researcher", "reviewer")
|
|
410
|
+
b.add_conditional_edges("reviewer", loop)
|
|
411
|
+
|
|
412
|
+
graph = monitor(b.compile()) # ← AgentSonar
|
|
413
|
+
graph.invoke({"messages": [HumanMessage(content="go")], "iteration": 0},
|
|
414
|
+
config={"recursion_limit": 50})
|
|
415
|
+
graph.shutdown() # ← AgentSonar
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
<a id="crewai-minimal-setup"></a>
|
|
419
|
+
### CrewAI minimal setup
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
# pip install agentsonar[crewai]
|
|
423
|
+
# export OPENAI_API_KEY=sk-...
|
|
424
|
+
from crewai import Agent, Crew, Process, Task
|
|
425
|
+
|
|
426
|
+
from agentsonar import AgentSonarListener # ← AgentSonar
|
|
427
|
+
sonar = AgentSonarListener() # ← AgentSonar
|
|
428
|
+
|
|
429
|
+
researcher = Agent(role="Researcher", goal="Gather info on the topic.",
|
|
430
|
+
backstory="Senior researcher.", allow_delegation=False)
|
|
431
|
+
writer = Agent(role="Writer", goal="Write a short summary.",
|
|
432
|
+
backstory="Technical writer.", allow_delegation=False)
|
|
433
|
+
manager = Agent(role="Manager", goal="Coordinate researcher and writer.",
|
|
434
|
+
backstory="Project manager.", allow_delegation=True)
|
|
435
|
+
|
|
436
|
+
task = Task(
|
|
437
|
+
description="Write a 3-sentence summary of multi-agent coordination. "
|
|
438
|
+
"Delegate research, then writing.",
|
|
439
|
+
expected_output="A 3-sentence summary.",
|
|
440
|
+
agent=manager,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# In hierarchical mode the manager goes ONLY in `manager_agent`,
|
|
444
|
+
# never in `agents`. Workers go in `agents`.
|
|
445
|
+
Crew(agents=[researcher, writer], tasks=[task],
|
|
446
|
+
process=Process.hierarchical, manager_agent=manager).kickoff()
|
|
447
|
+
# AgentSonarListener auto-shuts down on CrewKickoffCompletedEvent.
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
After either script finishes, open the HTML report:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
open "agentsonar_logs/$(cat agentsonar_logs/latest)/report.html"
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Status
|
|
457
|
+
|
|
458
|
+
Closed beta. Schema, public API, and output formats are stable for
|
|
459
|
+
design partners.
|
|
460
|
+
|
|
461
|
+
## License
|
|
462
|
+
|
|
463
|
+
Apache-2.0
|