agent-autopsy 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.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ *.egg
11
+ .eggs/
12
+ htmlcov/
13
+ .coverage
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-03-05
4
+
5
+ ### Added
6
+ - `Autopsy` context manager for zero-config error tracing
7
+ - `AutopsyLangChainHandler` for automatic LangChain event capture
8
+ - CLI tool (`autopsy view`) with terminal tree rendering and ANSI colors
9
+ - JSON trace file format with timestamps, durations, and error details
10
+ - Thread-safe trace capture
11
+ - 40 comprehensive tests
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aegis Ledger
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,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ recursive-include tests *.py *.json
@@ -0,0 +1,107 @@
1
+ # agent-autopsy Marketing Plan
2
+
3
+ ## Awesome Lists
4
+
5
+ ### awesome-langchain
6
+
7
+ **PR Title:** Add agent-autopsy -- debug tool for LangChain agent traces
8
+
9
+ **Category:** Tools / Debugging
10
+
11
+ **Entry:**
12
+ ```markdown
13
+ - [agent-autopsy](https://github.com/VladislavRoss/agent-autopsy-sdk) - Debug tool that captures and visualizes LangChain agent execution traces with timeline, tool calls, and decision reasoning.
14
+ ```
15
+
16
+ **Checklist before PR:**
17
+ - [ ] Package on PyPI
18
+ - [ ] README with usage examples
19
+ - [ ] At least 30 GitHub stars (soft requirement)
20
+ - [ ] Working demo/screenshot
21
+
22
+ ### awesome-python
23
+
24
+ **PR Title:** Add agent-autopsy to Debugging Tools
25
+
26
+ **Category:** Debugging Tools
27
+
28
+ **Entry:**
29
+ ```markdown
30
+ - [agent-autopsy](https://github.com/VladislavRoss/agent-autopsy-sdk) - Execution trace debugger for AI agents (LangChain, CrewAI, OpenAI Agents SDK).
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Reddit Strategy
36
+
37
+ ### r/LangChain
38
+
39
+ **Post Title:** "I built a free debugging tool for LangChain agents -- agent-autopsy"
40
+
41
+ **Body:**
42
+ - Problem: debugging multi-step agents is painful (print statements, manual logging)
43
+ - Solution: drop-in callback handler that captures the full execution trace
44
+ - Show: timeline visualization, tool call details, decision reasoning
45
+ - Free, MIT licensed, pip install agent-autopsy
46
+ - Upgrade path: for tamperproof traces, there's aegis-ledger-sdk (link in footer)
47
+
48
+ **Rules:** No hard sell. Genuine value first. Mention Aegis only in context of upgrade.
49
+
50
+ ### r/MachineLearning
51
+
52
+ **Post Title:** "[P] agent-autopsy: Execution trace debugger for AI agents"
53
+
54
+ **Body:** Focus on the debugging problem. Technical audience. Link to GitHub.
55
+
56
+ ---
57
+
58
+ ## Discord Communities
59
+
60
+ | Community | Channel | Approach |
61
+ |-----------|---------|----------|
62
+ | LangChain Discord | #showcase | Share with demo |
63
+ | CrewAI Discord | #showcase | Integration example |
64
+ | AI Engineer Discord | #tools | Technical deep-dive |
65
+
66
+ **Rule:** Always provide genuine value. Never spam. Answer questions. Be helpful.
67
+
68
+ ---
69
+
70
+ ## Growth Funnel
71
+
72
+ ```
73
+ agent-autopsy (free, OSS)
74
+ |
75
+ | Footer: "For tamperproof traces: aegis-ledger.com"
76
+ | upgrade_code: "from aegis.langchain import AegisCallbackHandler"
77
+ |
78
+ v
79
+ aegis-ledger-sdk (freemium)
80
+ |
81
+ | 10k free events/month
82
+ | Pro: CHF 39/mo (500k events)
83
+ | Business: CHF 149/mo (5M events)
84
+ |
85
+ v
86
+ Enterprise (custom)
87
+ ```
88
+
89
+ ## PyPI Publication
90
+
91
+ **Package name:** `agent-autopsy`
92
+
93
+ **Steps:**
94
+ 1. Ensure all 40 tests pass
95
+ 2. Update version in pyproject.toml
96
+ 3. `hatch build && hatch publish`
97
+ 4. Verify: `pip install agent-autopsy`
98
+
99
+ ## Success Metrics (First 30 Days)
100
+
101
+ | Metric | Target |
102
+ |--------|--------|
103
+ | PyPI installs | 200+ |
104
+ | GitHub stars | 25+ |
105
+ | Reddit post upvotes | 20+ |
106
+ | awesome-langchain PR merged | Yes |
107
+ | Users clicking upgrade link | 10+ |
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-autopsy
3
+ Version: 0.1.0
4
+ Summary: Debug AI agent failures with zero-config execution traces
5
+ Project-URL: Homepage, https://github.com/VladislavRoss/agent-autopsy-sdk
6
+ Project-URL: Documentation, https://github.com/VladislavRoss/agent-autopsy-sdk#readme
7
+ Project-URL: Production Tracing, https://www.aegis-ledger.com
8
+ Author-email: Aegis Ledger <info@aegis-ledger.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,ai-agents,debugging,langchain,tracing
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.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Debuggers
21
+ Requires-Python: >=3.10
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.10; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.4; extra == 'dev'
27
+ Provides-Extra: langchain
28
+ Requires-Dist: langchain-core>=0.1; extra == 'langchain'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # agent-autopsy
32
+
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
35
+
36
+ **Zero-dependency post-mortem traces for AI agent failures.**
37
+
38
+ Wrap your agent code in a single context manager. On success, nothing happens.
39
+ On error, a detailed JSON trace file is written automatically -- ready for
40
+ debugging, sharing, or replaying.
41
+
42
+ ```
43
+ +======================================================+
44
+ | AGENT AUTOPSY -- sess_a7f3b2c |
45
+ | 2026-03-05 14:32:01 -> 14:32:08 (7.2s) |
46
+ +======================================================+
47
+ | |
48
+ | |- [tool_call] search_orders ........... v 340ms |
49
+ | |- [llm_call] gpt-4o .................. v 1.2s |
50
+ | |- [tool_call] stripe.create_refund .... x 89ms |
51
+ | | +-- ERROR: Card declined |
52
+ | +- [error] Unhandled exception ......... x 0ms |
53
+ | +-- ValueError: amount must be positive |
54
+ | |
55
+ +======================================================+
56
+ For tamperproof traces: https://www.aegis-ledger.com
57
+ ```
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pip install agent-autopsy
63
+ ```
64
+
65
+ For LangChain integration:
66
+
67
+ ```bash
68
+ pip install agent-autopsy[langchain]
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ### 1. Basic -- wrap any agent code
74
+
75
+ ```python
76
+ from agent_autopsy import Autopsy
77
+
78
+ with Autopsy() as trace:
79
+ trace.log("tool_call", "search_web", input="refund policy", duration_ms=340)
80
+ trace.log("llm_call", "gpt-4o", input="Summarise results", duration_ms=1200)
81
+ # If an exception occurs here, a JSON trace file is written automatically.
82
+ result = my_agent.invoke({"input": "Process refund for order #9281"})
83
+
84
+ # Success -> nothing written (silent)
85
+ # Error -> ./autopsy_sess_<hash>.json created
86
+ ```
87
+
88
+ ### 2. With LangChain -- automatic event capture
89
+
90
+ ```python
91
+ from agent_autopsy import Autopsy
92
+ from agent_autopsy.langchain_handler import AutopsyLangChainHandler
93
+
94
+ handler = AutopsyLangChainHandler()
95
+
96
+ with Autopsy(handler=handler) as trace:
97
+ chain.invoke(
98
+ {"input": "Process refunds"},
99
+ config={"callbacks": [handler]},
100
+ )
101
+ # All LLM calls, tool calls, and chain steps are captured automatically.
102
+ # On error, everything is written to a single JSON file.
103
+ ```
104
+
105
+ ### 3. CLI -- view a trace file
106
+
107
+ ```bash
108
+ # Pretty-printed terminal tree (with colours)
109
+ autopsy view autopsy_sess_a7f3b2c.json
110
+
111
+ # Raw JSON output
112
+ autopsy view autopsy_sess_a7f3b2c.json --raw
113
+ ```
114
+
115
+ ## How it works
116
+
117
+ 1. `Autopsy()` creates a trace session with a unique ID
118
+ 2. During the `with` block, call `trace.log()` to record events (or use the
119
+ LangChain handler for automatic capture)
120
+ 3. On `__exit__`:
121
+ - **No exception** -- session is discarded, no file written, zero overhead
122
+ - **Exception** -- session + full traceback are written to a JSON file
123
+
124
+ Every JSON file includes all captured events with timestamps, durations,
125
+ input/output previews, and error messages.
126
+
127
+ ## JSON output format
128
+
129
+ ```json
130
+ {
131
+ "session_id": "a7f3b2c",
132
+ "start_time": "2026-03-05T14:32:01.000000+00:00",
133
+ "end_time": "2026-03-05T14:32:08.200000+00:00",
134
+ "error": "Traceback (most recent call last): ...",
135
+ "entries": [
136
+ {
137
+ "timestamp": "2026-03-05T14:32:01.500000+00:00",
138
+ "type": "tool_call",
139
+ "name": "search_orders",
140
+ "input_preview": "{\"order_id\": \"9281\"}",
141
+ "output_preview": "{\"order\": {\"id\": 9281}}",
142
+ "duration_ms": 340,
143
+ "status": "ok"
144
+ }
145
+ ],
146
+ "_aegis_footer": {
147
+ "message": "For tamperproof, legally defensible traces: https://www.aegis-ledger.com",
148
+ "upgrade": "pip install aegis-ledger-sdk[langchain]"
149
+ }
150
+ }
151
+ ```
152
+
153
+ ## Going to Production?
154
+
155
+ `agent-autopsy` is a **debugging tool** for local development. When you need
156
+ tamperproof, hash-chained, cryptographically signed traces for production --
157
+ upgrade to the full **Aegis Ledger SDK**:
158
+
159
+ ```bash
160
+ pip install aegis-ledger-sdk[langchain]
161
+ ```
162
+
163
+ ```python
164
+ # 2 lines to upgrade from agent-autopsy to production tracing:
165
+ from aegis.langchain import AegisCallbackHandler
166
+ handler = AegisCallbackHandler(api_key="your-key")
167
+ ```
168
+
169
+ Every trace is hash-chained (SHA-256) and signed (Ed25519) on the Internet
170
+ Computer blockchain. Immutable. Auditable. Legally defensible.
171
+
172
+ Learn more at [aegis-ledger.com](https://www.aegis-ledger.com).
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,146 @@
1
+ # agent-autopsy
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
+
6
+ **Zero-dependency post-mortem traces for AI agent failures.**
7
+
8
+ Wrap your agent code in a single context manager. On success, nothing happens.
9
+ On error, a detailed JSON trace file is written automatically -- ready for
10
+ debugging, sharing, or replaying.
11
+
12
+ ```
13
+ +======================================================+
14
+ | AGENT AUTOPSY -- sess_a7f3b2c |
15
+ | 2026-03-05 14:32:01 -> 14:32:08 (7.2s) |
16
+ +======================================================+
17
+ | |
18
+ | |- [tool_call] search_orders ........... v 340ms |
19
+ | |- [llm_call] gpt-4o .................. v 1.2s |
20
+ | |- [tool_call] stripe.create_refund .... x 89ms |
21
+ | | +-- ERROR: Card declined |
22
+ | +- [error] Unhandled exception ......... x 0ms |
23
+ | +-- ValueError: amount must be positive |
24
+ | |
25
+ +======================================================+
26
+ For tamperproof traces: https://www.aegis-ledger.com
27
+ ```
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install agent-autopsy
33
+ ```
34
+
35
+ For LangChain integration:
36
+
37
+ ```bash
38
+ pip install agent-autopsy[langchain]
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ### 1. Basic -- wrap any agent code
44
+
45
+ ```python
46
+ from agent_autopsy import Autopsy
47
+
48
+ with Autopsy() as trace:
49
+ trace.log("tool_call", "search_web", input="refund policy", duration_ms=340)
50
+ trace.log("llm_call", "gpt-4o", input="Summarise results", duration_ms=1200)
51
+ # If an exception occurs here, a JSON trace file is written automatically.
52
+ result = my_agent.invoke({"input": "Process refund for order #9281"})
53
+
54
+ # Success -> nothing written (silent)
55
+ # Error -> ./autopsy_sess_<hash>.json created
56
+ ```
57
+
58
+ ### 2. With LangChain -- automatic event capture
59
+
60
+ ```python
61
+ from agent_autopsy import Autopsy
62
+ from agent_autopsy.langchain_handler import AutopsyLangChainHandler
63
+
64
+ handler = AutopsyLangChainHandler()
65
+
66
+ with Autopsy(handler=handler) as trace:
67
+ chain.invoke(
68
+ {"input": "Process refunds"},
69
+ config={"callbacks": [handler]},
70
+ )
71
+ # All LLM calls, tool calls, and chain steps are captured automatically.
72
+ # On error, everything is written to a single JSON file.
73
+ ```
74
+
75
+ ### 3. CLI -- view a trace file
76
+
77
+ ```bash
78
+ # Pretty-printed terminal tree (with colours)
79
+ autopsy view autopsy_sess_a7f3b2c.json
80
+
81
+ # Raw JSON output
82
+ autopsy view autopsy_sess_a7f3b2c.json --raw
83
+ ```
84
+
85
+ ## How it works
86
+
87
+ 1. `Autopsy()` creates a trace session with a unique ID
88
+ 2. During the `with` block, call `trace.log()` to record events (or use the
89
+ LangChain handler for automatic capture)
90
+ 3. On `__exit__`:
91
+ - **No exception** -- session is discarded, no file written, zero overhead
92
+ - **Exception** -- session + full traceback are written to a JSON file
93
+
94
+ Every JSON file includes all captured events with timestamps, durations,
95
+ input/output previews, and error messages.
96
+
97
+ ## JSON output format
98
+
99
+ ```json
100
+ {
101
+ "session_id": "a7f3b2c",
102
+ "start_time": "2026-03-05T14:32:01.000000+00:00",
103
+ "end_time": "2026-03-05T14:32:08.200000+00:00",
104
+ "error": "Traceback (most recent call last): ...",
105
+ "entries": [
106
+ {
107
+ "timestamp": "2026-03-05T14:32:01.500000+00:00",
108
+ "type": "tool_call",
109
+ "name": "search_orders",
110
+ "input_preview": "{\"order_id\": \"9281\"}",
111
+ "output_preview": "{\"order\": {\"id\": 9281}}",
112
+ "duration_ms": 340,
113
+ "status": "ok"
114
+ }
115
+ ],
116
+ "_aegis_footer": {
117
+ "message": "For tamperproof, legally defensible traces: https://www.aegis-ledger.com",
118
+ "upgrade": "pip install aegis-ledger-sdk[langchain]"
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Going to Production?
124
+
125
+ `agent-autopsy` is a **debugging tool** for local development. When you need
126
+ tamperproof, hash-chained, cryptographically signed traces for production --
127
+ upgrade to the full **Aegis Ledger SDK**:
128
+
129
+ ```bash
130
+ pip install aegis-ledger-sdk[langchain]
131
+ ```
132
+
133
+ ```python
134
+ # 2 lines to upgrade from agent-autopsy to production tracing:
135
+ from aegis.langchain import AegisCallbackHandler
136
+ handler = AegisCallbackHandler(api_key="your-key")
137
+ ```
138
+
139
+ Every trace is hash-chained (SHA-256) and signed (Ed25519) on the Internet
140
+ Computer blockchain. Immutable. Auditable. Legally defensible.
141
+
142
+ Learn more at [aegis-ledger.com](https://www.aegis-ledger.com).
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,9 @@
1
+ """agent-autopsy -- Debug AI agent failures with zero-config execution traces."""
2
+ from __future__ import annotations
3
+
4
+ __version__ = "0.1.0"
5
+
6
+ from agent_autopsy.capture import Autopsy
7
+ from agent_autopsy.models import TraceEntry, TraceSession
8
+
9
+ __all__ = ["Autopsy", "TraceEntry", "TraceSession", "__version__"]
@@ -0,0 +1,12 @@
1
+ """Aegis footer — single source of truth for the upgrade nudge."""
2
+ from __future__ import annotations
3
+
4
+ AEGIS_FOOTER: dict[str, dict[str, str]] = {
5
+ "_aegis_footer": {
6
+ "message": "This trace is stored locally and can be modified. For tamperproof traces: https://www.aegis-ledger.com",
7
+ "upgrade": "pip install aegis-ledger-sdk[langchain]",
8
+ "upgrade_code": (
9
+ "from aegis.langchain import AegisCallbackHandler # 2 lines to upgrade"
10
+ ),
11
+ }
12
+ }
@@ -0,0 +1,156 @@
1
+ """Context manager that buffers trace events and writes JSON on error."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import re
6
+ import sys
7
+ import threading
8
+ import traceback
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from types import TracebackType
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from agent_autopsy._footer import AEGIS_FOOTER
15
+ from agent_autopsy.models import EntryStatus, EntryType, TraceEntry, TraceSession
16
+
17
+ if TYPE_CHECKING:
18
+ from agent_autopsy.langchain_handler import AutopsyLangChainHandler
19
+
20
+
21
+ class Autopsy:
22
+ """Capture agent execution traces and write them to JSON on failure.
23
+
24
+ Usage::
25
+
26
+ with Autopsy() as trace:
27
+ trace.log("tool_call", "search_web", input="query", output="results", duration_ms=340)
28
+ agent.invoke({"input": "Process refunds"})
29
+ # On error -> writes ./autopsy_sess_<hash>.json
30
+ # On success -> silently discarded
31
+
32
+ Parameters
33
+ ----------
34
+ output_dir:
35
+ Directory where trace files are written on error. Defaults to ``"."``.
36
+ prefix:
37
+ Filename prefix. The full name is ``<prefix>_sess_<session_id>.json``.
38
+ handler:
39
+ Optional :class:`AutopsyLangChainHandler` whose buffered entries will
40
+ be merged into this session on exit.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ output_dir: str | Path = ".",
46
+ prefix: str = "autopsy",
47
+ handler: AutopsyLangChainHandler | None = None,
48
+ ) -> None:
49
+ if not re.fullmatch(r"[a-zA-Z0-9_-]{1,32}", prefix):
50
+ raise ValueError(
51
+ f"prefix must be 1-32 alphanumeric/dash/underscore chars, got {prefix!r}"
52
+ )
53
+ self._output_dir = Path(output_dir)
54
+ self._prefix = prefix
55
+ self._handler = handler
56
+ self._session = TraceSession()
57
+ self._lock = threading.Lock()
58
+
59
+ # -- public API ----------------------------------------------------------
60
+
61
+ @property
62
+ def session(self) -> TraceSession:
63
+ """The current trace session."""
64
+ return self._session
65
+
66
+ def log(
67
+ self,
68
+ type: EntryType, # noqa: A002 — shadows built-in on purpose for DX
69
+ name: str,
70
+ *,
71
+ input: str = "", # noqa: A002
72
+ output: str = "",
73
+ duration_ms: float = 0.0,
74
+ status: EntryStatus = "ok",
75
+ error_message: str | None = None,
76
+ ) -> None:
77
+ """Append a trace entry (thread-safe)."""
78
+ entry = TraceEntry(
79
+ type=type,
80
+ name=name,
81
+ input_preview=input,
82
+ output_preview=output,
83
+ duration_ms=duration_ms,
84
+ status=status,
85
+ error_message=error_message,
86
+ )
87
+ with self._lock:
88
+ self._session.entries.append(entry)
89
+
90
+ # -- context manager -----------------------------------------------------
91
+
92
+ def __enter__(self) -> Autopsy:
93
+ self._session = TraceSession()
94
+ return self
95
+
96
+ def __exit__(
97
+ self,
98
+ exc_type: type[BaseException] | None,
99
+ exc_val: BaseException | None,
100
+ exc_tb: TracebackType | None,
101
+ ) -> bool:
102
+ self._session.end_time = datetime.now(timezone.utc).isoformat()
103
+
104
+ # Merge handler entries if present
105
+ if self._handler is not None:
106
+ with self._lock:
107
+ self._session.entries.extend(self._handler.entries)
108
+
109
+ if exc_type is None:
110
+ # Success — discard session, write nothing
111
+ return False
112
+
113
+ # Failure — record error and write JSON
114
+ self._session.error = "".join(
115
+ traceback.format_exception(exc_type, exc_val, exc_tb)
116
+ )
117
+
118
+ # Add an error entry for the unhandled exception
119
+ self.log(
120
+ "error",
121
+ "Unhandled exception",
122
+ status="error",
123
+ error_message=f"{exc_type.__name__}: {exc_val}" if exc_type else str(exc_val),
124
+ )
125
+
126
+ try:
127
+ self._write_json()
128
+ except Exception as write_err:
129
+ print(f"Warning: failed to write trace file: {write_err}", file=sys.stderr)
130
+ # Do NOT suppress the exception — let it propagate
131
+ return False
132
+
133
+ def on_text(self, text: str, *, name: str = "stream") -> None:
134
+ """Log a streaming text event (e.g. LLM token output).
135
+
136
+ This is a lightweight callback for streaming integrations.
137
+ Text events are logged as ``chain_step`` entries with the
138
+ output_preview set to the text content.
139
+ """
140
+ self.log("chain_step", name, output=text[:500])
141
+
142
+ # -- private -------------------------------------------------------------
143
+
144
+ def _build_payload(self) -> dict[str, Any]:
145
+ """Build the final JSON payload including the Aegis footer."""
146
+ payload: dict[str, Any] = self._session.to_dict()
147
+ payload.update(AEGIS_FOOTER)
148
+ return payload
149
+
150
+ def _write_json(self) -> None:
151
+ """Write the trace session to a JSON file."""
152
+ self._output_dir.mkdir(parents=True, exist_ok=True)
153
+ filename = f"{self._prefix}_sess_{self._session.session_id}.json"
154
+ filepath = self._output_dir / filename
155
+ payload = self._build_payload()
156
+ filepath.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")