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
@@ -0,0 +1,59 @@
1
+ """Traceforge SDK — pipeline setup and gating.
2
+
3
+ Usage:
4
+ from traceforge.sdk import Pipeline, GatePolicy, Verdict, ToolCallRequest
5
+
6
+ def my_preflight(request: ToolCallRequest, ctx: GateContext) -> Verdict:
7
+ if request.risk_score > 60:
8
+ return Verdict.deny(f"score {request.risk_score} exceeds threshold")
9
+ return Verdict.allow()
10
+
11
+ policy = GatePolicy().preflight(my_preflight)
12
+
13
+ pipeline = Pipeline.create(policy=policy)
14
+ pipeline.gate_crewai() # CrewAI hooks
15
+ pipeline.gate_langchain(tool) # LangChain tool wrap
16
+ pipeline.gate_maf() # MAF middleware
17
+
18
+ The preflight callback returns a Verdict. Traceforge enforces it using each framework's
19
+ native blocking mechanism. The postflight callback receives the tool output for audit.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from traceforge.sdk.gate_policy import GatePolicy # noqa: E402
25
+ from traceforge.sdk.gate_types import ( # noqa: E402
26
+ GateContext,
27
+ PostflightAction,
28
+ PostflightVerdict,
29
+ ToolCallRequest,
30
+ ToolCallResult,
31
+ )
32
+ from traceforge.sdk.verdict import ( # noqa: E402
33
+ Decision,
34
+ PostflightGate,
35
+ PreflightGate,
36
+ Verdict,
37
+ )
38
+ from traceforge.trace import EventTrace, TraceStage # noqa: E402
39
+
40
+ # Imported last so every SDK submodule symbol above is already defined before the
41
+ # facade (which pulls in the governance engine + core EventPipeline) resolves,
42
+ # keeping this package free of import-cycle ordering hazards.
43
+ from traceforge.sdk.pipeline import Pipeline # noqa: E402
44
+
45
+ __all__ = [
46
+ "Pipeline",
47
+ "EventTrace",
48
+ "TraceStage",
49
+ "Verdict",
50
+ "Decision",
51
+ "PreflightGate",
52
+ "PostflightGate",
53
+ "GatePolicy",
54
+ "GateContext",
55
+ "ToolCallRequest",
56
+ "ToolCallResult",
57
+ "PostflightVerdict",
58
+ "PostflightAction",
59
+ ]
@@ -0,0 +1,63 @@
1
+ """GatePolicy — composable, testable gate registration.
2
+
3
+ Gates are registered via a GatePolicy which owns the ordered chain
4
+ of preflight/postflight gates. The pipeline uses this instead of
5
+ per-attach kwargs.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from traceforge.sdk.verdict import PostflightGate, PreflightGate
15
+
16
+
17
+ @dataclass
18
+ class GatePolicy:
19
+ """Ordered collection of gates. Testable without a pipeline.
20
+
21
+ Usage:
22
+ policy = (
23
+ GatePolicy()
24
+ .preflight(block_destructive_shell)
25
+ .preflight(rate_limit_gate)
26
+ .postflight(redact_secrets)
27
+ )
28
+
29
+ pipeline = Pipeline.create(policy=policy)
30
+
31
+ Gates run in registration order (top-down).
32
+ """
33
+
34
+ _preflight: list["PreflightGate"] = field(default_factory=list)
35
+ _postflight: list["PostflightGate"] = field(default_factory=list)
36
+
37
+ def preflight(self, gate: "PreflightGate") -> "GatePolicy":
38
+ """Register a preflight gate. Runs in registration order."""
39
+ self._preflight.append(gate)
40
+ return self
41
+
42
+ def postflight(self, gate: "PostflightGate") -> "GatePolicy":
43
+ """Register a postflight gate. Runs in registration order."""
44
+ self._postflight.append(gate)
45
+ return self
46
+
47
+ @property
48
+ def preflight_gates(self) -> tuple["PreflightGate", ...]:
49
+ """Ordered preflight gates (registration order)."""
50
+ return tuple(self._preflight)
51
+
52
+ @property
53
+ def postflight_gates(self) -> tuple["PostflightGate", ...]:
54
+ """Ordered postflight gates (registration order)."""
55
+ return tuple(self._postflight)
56
+
57
+ @property
58
+ def has_preflight(self) -> bool:
59
+ return len(self._preflight) > 0
60
+
61
+ @property
62
+ def has_postflight(self) -> bool:
63
+ return len(self._postflight) > 0
@@ -0,0 +1,140 @@
1
+ """Gate-facing types: view objects, context, and postflight verdict.
2
+
3
+ Gate authors import from here. These types provide the policy-relevant surface
4
+ with guaranteed non-optional fields — no None checks needed in gate logic.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import StrEnum
11
+ from types import MappingProxyType
12
+ from typing import TYPE_CHECKING
13
+
14
+ from traceforge._generated import (
15
+ Action,
16
+ Capability,
17
+ Effect,
18
+ Mechanism,
19
+ Recommendation,
20
+ RiskBand,
21
+ Role,
22
+ Scope,
23
+ )
24
+ from traceforge.sdk.verdict import Verdict
25
+ from traceforge.trace import EMPTY_MAP
26
+
27
+ if TYPE_CHECKING:
28
+ from traceforge.trace import EventTrace
29
+
30
+
31
+ # ─── Gate View Objects ────────────────────────────────────────────────────────
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class ToolCallRequest:
36
+ """What preflight gate authors receive. Policy-focused, no None checks.
37
+
38
+ All classification/assessment fields are guaranteed non-None — the pipeline
39
+ only constructs this for fully-assessed EventTraces.
40
+ """
41
+
42
+ # What tool is being called
43
+ tool: str
44
+ input: MappingProxyType
45
+ target: str | None
46
+
47
+ # How it's classified
48
+ mechanism: Mechanism
49
+ effect: Effect
50
+ capabilities: tuple[Capability, ...]
51
+ scope: tuple[Scope, ...]
52
+ role: tuple[Role, ...]
53
+ action: tuple[Action, ...]
54
+
55
+ # How dangerous
56
+ risk_score: int
57
+ risk_band: RiskBand
58
+ suggested_action: Recommendation
59
+ reason: str
60
+
61
+ # Identity
62
+ session_id: str
63
+ tool_call_id: str
64
+
65
+ # Escape hatch — full EventTrace for raw_event, attributes, etc.
66
+ event_trace: EventTrace
67
+
68
+
69
+ @dataclass(frozen=True, slots=True)
70
+ class ToolCallResult:
71
+ """What postflight gate authors receive. Includes tool output."""
72
+
73
+ # What tool ran
74
+ tool: str
75
+ input: MappingProxyType
76
+ target: str | None
77
+
78
+ # What it produced
79
+ output: MappingProxyType
80
+ duration_ms: int | None
81
+ error: str | None
82
+
83
+ # Classification + assessment
84
+ mechanism: Mechanism
85
+ effect: Effect
86
+ capabilities: tuple[Capability, ...]
87
+ risk_score: int
88
+ risk_band: RiskBand
89
+ suggested_action: Recommendation
90
+ reason: str
91
+
92
+ # Identity
93
+ session_id: str
94
+ tool_call_id: str
95
+
96
+ # Escape hatch
97
+ event_trace: EventTrace
98
+
99
+
100
+ # ─── GateContext ──────────────────────────────────────────────────────────────
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class GateContext:
105
+ """Session-scoped state passed as second arg to every gate.
106
+
107
+ Pipeline builds and maintains this per session. Gate authors can
108
+ ignore it for stateless policies.
109
+ """
110
+
111
+ session_id: str
112
+ tool_call_count: int
113
+ denied_count: int
114
+ prior_verdicts: tuple[Verdict, ...] = ()
115
+ prior_tool_call_ids: tuple[str, ...] = ()
116
+ agent_id: str | None = None
117
+ user_id: str | None = None
118
+ policy: MappingProxyType = field(default_factory=lambda: EMPTY_MAP)
119
+
120
+
121
+ # ─── Postflight Verdict ───────────────────────────────────────────────────────
122
+
123
+
124
+ class PostflightAction(StrEnum):
125
+ """What to do with tool output after execution."""
126
+
127
+ ACCEPT = "accept"
128
+ REDACT = "redact"
129
+ SUPPRESS = "suppress"
130
+ TERMINATE = "terminate"
131
+ ALERT = "alert"
132
+
133
+
134
+ @dataclass(frozen=True, slots=True)
135
+ class PostflightVerdict:
136
+ """Returned by postflight gates. Controls what happens to tool output."""
137
+
138
+ action: PostflightAction = PostflightAction.ACCEPT
139
+ reason: str = ""
140
+ redaction_keys: tuple[str, ...] = ()
@@ -0,0 +1,265 @@
1
+ """The traceforge Pipeline: observe -> enrich -> classify -> structure -> [govern] -> sinks.
2
+
3
+ This is the SDK's top-level entry point. It composes traceforge's two halves into one
4
+ object:
5
+
6
+ * the **observation backbone** (:class:`~traceforge.pipeline.EventPipeline`), which
7
+ enriches, classifies, and ML-structures (phase / boundary / title) every event before
8
+ fanning it out to sinks, and
9
+ * the **governance engine** (:class:`~traceforge.governance.pipeline.GovernancePipeline`),
10
+ which scores risk, tracks budgets / drift, and backs the opt-in gating layer.
11
+
12
+ Governance is wired in as **one stage** of the backbone: when enabled, each event is
13
+ scored and its :class:`~traceforge.governance.results.SessionMeta` stamped onto
14
+ ``event.metadata.governance`` just before the sinks see it. It is not a separate pipeline
15
+ and not a precondition -- structuring runs with or without it.
16
+
17
+ Gating is a separate, opt-in enforcement layer. The ``gate_*`` helpers install a
18
+ :class:`~traceforge.sdk.GatePolicy` into your agent framework using that framework's native
19
+ blocking mechanism; the policy's preflight callback returns a
20
+ :class:`~traceforge.sdk.Verdict` (ALLOW / DENY). Observation itself never issues verdicts --
21
+ only the gating layer does, and only when you opt in.
22
+
23
+ Usage -- gating a live agent::
24
+
25
+ from traceforge.sdk import Pipeline, GatePolicy, Verdict, ToolCallRequest, GateContext
26
+
27
+ def preflight(request: ToolCallRequest, ctx: GateContext) -> Verdict:
28
+ if request.risk_score > 60:
29
+ return Verdict.deny(f"score {request.risk_score} exceeds threshold")
30
+ return Verdict.allow()
31
+
32
+ pipeline = Pipeline.create(policy=GatePolicy().preflight(preflight))
33
+ tool = pipeline.gate_langchain(tool) # wrap a LangChain tool
34
+ pipeline.gate_crewai() # install CrewAI hooks
35
+
36
+ Usage -- observing an event stream::
37
+
38
+ from traceforge.sdk import Pipeline
39
+ from traceforge.sinks.jsonl import JsonlSink
40
+
41
+ async with Pipeline.create(sinks=[JsonlSink("events.jsonl")]) as pipeline:
42
+ async for event in adapter.stream(...):
43
+ await pipeline.push(event) # enriched, structured, governed, emitted
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ from typing import TYPE_CHECKING
49
+
50
+ from traceforge.enricher import Enricher
51
+ from traceforge.governance.pipeline import GovernancePipeline
52
+ from traceforge.pipeline import EventPipeline
53
+
54
+ if TYPE_CHECKING:
55
+ from traceforge.config.models import GovernanceConfig
56
+ from traceforge.sdk.gate_policy import GatePolicy
57
+ from traceforge.sinks.base import StorageSink
58
+ from traceforge.trace import EventTrace
59
+ from traceforge.types import SessionEvent, TelemetrySpan, UsageRecord
60
+
61
+
62
+ class Pipeline:
63
+ """Observe -> structure backbone with governance as one stage and opt-in gating.
64
+
65
+ Construct via :meth:`create` or :meth:`from_config`; do not instantiate directly.
66
+ """
67
+
68
+ def __init__(self, *, backbone: EventPipeline, governance: GovernancePipeline) -> None:
69
+ self._backbone = backbone
70
+ self._governance = governance
71
+
72
+ # ---- construction -------------------------------------------------------
73
+
74
+ @classmethod
75
+ def create(
76
+ cls,
77
+ config: "GovernanceConfig | None" = None,
78
+ *,
79
+ policy: "GatePolicy | None" = None,
80
+ sinks: "list[StorageSink] | None" = None,
81
+ enable_structure: bool = True,
82
+ enable_title: bool = False,
83
+ enricher: "Enricher | None" = None,
84
+ governance: bool = True,
85
+ ) -> "Pipeline":
86
+ """Build a pipeline from config.
87
+
88
+ Args:
89
+ config: GovernanceConfig for the governance engine (in-memory DB and
90
+ sensible defaults when omitted).
91
+ policy: Optional GatePolicy enabling the gating layer (the ``gate_*``
92
+ helpers). Omit for observation-only usage.
93
+ sinks: Observation destinations for pushed events. Omit for gating-only
94
+ usage (you never call :meth:`push`).
95
+ enable_structure: Run phase + boundary ML structuring on pushed events
96
+ (default True). Models load lazily on first push, so gating-only
97
+ usage pays nothing.
98
+ enable_title: Also run session-title inference (default False).
99
+ enricher: Custom :class:`~traceforge.enricher.Enricher`; defaults to a
100
+ zero-config ``Enricher()``.
101
+ governance: Wire the governance engine in as a pipeline stage so pushed
102
+ events get ``metadata.governance`` stamped (default True). Set False
103
+ for pure observation; ``gate_*`` / :meth:`score_tool_call` still use
104
+ the engine.
105
+ """
106
+ gov_engine = GovernancePipeline.create(config, policy=policy)
107
+ return cls._assemble(
108
+ gov_engine,
109
+ sinks=sinks,
110
+ enable_structure=enable_structure,
111
+ enable_title=enable_title,
112
+ enricher=enricher,
113
+ governance=governance,
114
+ )
115
+
116
+ @classmethod
117
+ def from_config(
118
+ cls,
119
+ path=None,
120
+ *,
121
+ policy: "GatePolicy | None" = None,
122
+ sinks: "list[StorageSink] | None" = None,
123
+ enable_structure: bool = True,
124
+ enable_title: bool = False,
125
+ enricher: "Enricher | None" = None,
126
+ governance: bool = True,
127
+ ) -> "Pipeline":
128
+ """Build a pipeline from a ``traceforge.yaml`` file.
129
+
130
+ Same knobs as :meth:`create`, but the governance engine is loaded from the
131
+ given config file (see :meth:`GovernancePipeline.from_config`).
132
+
133
+ Sink hydration (SDK/CLI parity, issue #116): when ``sinks`` is **omitted**
134
+ (left as ``None``), the standard sinks declared in the config's pipelines
135
+ (``pipelines[].sinks``) are auto-built from their ``SinkConfig`` entries via
136
+ the shared :func:`traceforge.sinks.factory.build_sinks` — the same factory the
137
+ ``traceforge watch`` daemon uses. Sinks declared across multiple named
138
+ pipelines are flattened into one list, since the SDK backbone is a single
139
+ source/adapter-agnostic event stream and every declared sink is a live
140
+ destination.
141
+
142
+ Programmatic sinks always win: passing ``sinks=[...]`` — **including an
143
+ explicit empty list ``[]``** — is honored exactly and disables
144
+ auto-hydration. ``None`` is the sole "omitted" sentinel.
145
+ """
146
+ gov_engine = GovernancePipeline.from_config(path, policy=policy)
147
+
148
+ if sinks is None:
149
+ from traceforge.config.loader import load_config_from_path
150
+ from traceforge.sinks.factory import build_sinks
151
+
152
+ config = load_config_from_path(path)
153
+ sinks = build_sinks(
154
+ [sink_config for pipeline in config.pipelines for sink_config in pipeline.sinks]
155
+ )
156
+
157
+ return cls._assemble(
158
+ gov_engine,
159
+ sinks=sinks,
160
+ enable_structure=enable_structure,
161
+ enable_title=enable_title,
162
+ enricher=enricher,
163
+ governance=governance,
164
+ )
165
+
166
+ @classmethod
167
+ def _assemble(
168
+ cls,
169
+ gov_engine: GovernancePipeline,
170
+ *,
171
+ sinks,
172
+ enable_structure,
173
+ enable_title,
174
+ enricher,
175
+ governance,
176
+ ) -> "Pipeline":
177
+ backbone = EventPipeline(
178
+ sinks=list(sinks) if sinks is not None else [],
179
+ enricher=enricher if enricher is not None else Enricher(),
180
+ enable_phase=enable_structure,
181
+ enable_boundary=enable_structure,
182
+ enable_title=enable_title,
183
+ governance=gov_engine if governance else None,
184
+ )
185
+ return cls(backbone=backbone, governance=gov_engine)
186
+
187
+ # ---- observation backbone ----------------------------------------------
188
+
189
+ async def push(self, event: "SessionEvent") -> None:
190
+ """Enrich, classify, structure, govern, and emit a single event."""
191
+ await self._backbone.push(event)
192
+
193
+ async def push_span(self, span: "TelemetrySpan") -> None:
194
+ """Emit a telemetry span to the sinks."""
195
+ await self._backbone.push_span(span)
196
+
197
+ async def push_usage(self, usage: "UsageRecord") -> None:
198
+ """Emit a usage record to the sinks."""
199
+ await self._backbone.push_usage(usage)
200
+
201
+ async def flush(self) -> None:
202
+ """Flush held plumbing and pending refinements, then flush all sinks."""
203
+ await self._backbone.flush()
204
+
205
+ async def close(self) -> None:
206
+ """Flush, then close all sinks."""
207
+ await self._backbone.close()
208
+
209
+ async def __aenter__(self) -> "Pipeline":
210
+ return self
211
+
212
+ async def __aexit__(self, *exc) -> None:
213
+ await self.close()
214
+
215
+ # ---- preflight scoring (governance stage, read-only) -------------------
216
+
217
+ def score_tool_call(self, payload: dict) -> "EventTrace":
218
+ """Score a prospective tool call (dict) without mutating session state."""
219
+ return self._governance.score_tool_call(payload)
220
+
221
+ # ---- gating layer (opt-in; delegates to the governance engine) ---------
222
+
223
+ def gate_crewai(self) -> None:
224
+ """Install CrewAI gating hooks."""
225
+ return self._governance.gate_crewai()
226
+
227
+ def gate_langchain(self, tool):
228
+ """Wrap a LangChain tool so calls are gated. Returns the wrapped tool."""
229
+ return self._governance.gate_langchain(tool)
230
+
231
+ def gate_langgraph(self, tools):
232
+ """Wrap LangGraph tools. Returns a gated tool node."""
233
+ return self._governance.gate_langgraph(tools)
234
+
235
+ def gate_semantic_kernel(self, kernel) -> None:
236
+ """Install a Semantic Kernel gating filter."""
237
+ return self._governance.gate_semantic_kernel(kernel)
238
+
239
+ def gate_maf(self):
240
+ """Return Microsoft Agent Framework gating middleware."""
241
+ return self._governance.gate_maf()
242
+
243
+ def gate_smolagents(self, agent_cls=None):
244
+ """Gate smolagents. Returns a gated agent class."""
245
+ return self._governance.gate_smolagents(agent_cls)
246
+
247
+ def gate_pydantic_ai(self, agent) -> None:
248
+ """Install Pydantic AI gating."""
249
+ return self._governance.gate_pydantic_ai(agent)
250
+
251
+ def gate_openai_agents(self, agent):
252
+ """Gate an OpenAI Agents SDK agent."""
253
+ return self._governance.gate_openai_agents(agent)
254
+
255
+ # ---- escape hatches ----------------------------------------------------
256
+
257
+ @property
258
+ def governance(self) -> GovernancePipeline:
259
+ """The underlying governance engine (scoring, budgets, gating internals)."""
260
+ return self._governance
261
+
262
+ @property
263
+ def backbone(self) -> EventPipeline:
264
+ """The underlying observation / structuring EventPipeline."""
265
+ return self._backbone
@@ -0,0 +1,81 @@
1
+ """Verdict types for tool-call gating decisions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
8
+
9
+ if TYPE_CHECKING:
10
+ from traceforge.sdk.gate_types import (
11
+ GateContext,
12
+ PostflightVerdict,
13
+ ToolCallRequest,
14
+ ToolCallResult,
15
+ )
16
+
17
+
18
+ # ─── Decision & Verdict ───────────────────────────────────────────────────────
19
+
20
+
21
+ class Decision(Enum):
22
+ """The outcome of a gating decision."""
23
+
24
+ ALLOW = "allow"
25
+ DENY = "deny"
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class Verdict:
30
+ """A gating decision returned by a gate callback.
31
+
32
+ Args:
33
+ decision: ALLOW or DENY.
34
+ reason: Human-readable reason propagated to the LLM on denial.
35
+ """
36
+
37
+ decision: Decision
38
+ reason: str = ""
39
+
40
+ @property
41
+ def allowed(self) -> bool:
42
+ return self.decision == Decision.ALLOW
43
+
44
+ @property
45
+ def denied(self) -> bool:
46
+ return self.decision == Decision.DENY
47
+
48
+ @staticmethod
49
+ def allow() -> Verdict:
50
+ """Convenience factory for ALLOW."""
51
+ return Verdict(decision=Decision.ALLOW)
52
+
53
+ @staticmethod
54
+ def deny(reason: str = "") -> Verdict:
55
+ """Convenience factory for DENY with reason."""
56
+ return Verdict(decision=Decision.DENY, reason=reason)
57
+
58
+
59
+ # ─── Callback Protocols ───────────────────────────────────────────────────────
60
+
61
+
62
+ @runtime_checkable
63
+ class PreflightGate(Protocol):
64
+ """Protocol for preflight gate callbacks.
65
+
66
+ Receives a ToolCallRequest (policy-focused view) and GateContext.
67
+ Must return a Verdict (ALLOW or DENY).
68
+ """
69
+
70
+ def __call__(self, request: "ToolCallRequest", ctx: "GateContext") -> Verdict: ...
71
+
72
+
73
+ @runtime_checkable
74
+ class PostflightGate(Protocol):
75
+ """Protocol for postflight gate callbacks.
76
+
77
+ Receives a ToolCallResult (includes tool output) and GateContext.
78
+ Must return a PostflightVerdict.
79
+ """
80
+
81
+ def __call__(self, result: "ToolCallResult", ctx: "GateContext") -> "PostflightVerdict": ...
@@ -0,0 +1,23 @@
1
+ """Storage sinks for the traceforge pipeline."""
2
+
3
+ from traceforge.sinks.base import StorageSink
4
+ from traceforge.sinks.callback import CallbackSink
5
+ from traceforge.sinks.console import ConsoleSink
6
+ from traceforge.sinks.jsonl import JsonlSink
7
+ from traceforge.sinks.otel_exporter import OtelExporterSink
8
+ from traceforge.sinks.parquet import ParquetSink
9
+ from traceforge.sinks.s3 import S3Sink
10
+ from traceforge.sinks.sqlite_output import SqliteOutputSink
11
+ from traceforge.sinks.webhook import WebhookSink
12
+
13
+ __all__ = [
14
+ "StorageSink",
15
+ "CallbackSink",
16
+ "ConsoleSink",
17
+ "JsonlSink",
18
+ "OtelExporterSink",
19
+ "ParquetSink",
20
+ "S3Sink",
21
+ "SqliteOutputSink",
22
+ "WebhookSink",
23
+ ]