singleaxis-fabric 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.
Files changed (38) hide show
  1. singleaxis_fabric-0.1.0/.gitignore +108 -0
  2. singleaxis_fabric-0.1.0/LICENSE +15 -0
  3. singleaxis_fabric-0.1.0/PKG-INFO +257 -0
  4. singleaxis_fabric-0.1.0/README.md +217 -0
  5. singleaxis_fabric-0.1.0/pyproject.toml +117 -0
  6. singleaxis_fabric-0.1.0/src/fabric/__init__.py +59 -0
  7. singleaxis_fabric-0.1.0/src/fabric/_chain.py +91 -0
  8. singleaxis_fabric-0.1.0/src/fabric/_uds.py +48 -0
  9. singleaxis_fabric-0.1.0/src/fabric/_version.py +3 -0
  10. singleaxis_fabric-0.1.0/src/fabric/adapters/__init__.py +15 -0
  11. singleaxis_fabric-0.1.0/src/fabric/adapters/agent_framework.py +174 -0
  12. singleaxis_fabric-0.1.0/src/fabric/adapters/crewai.py +157 -0
  13. singleaxis_fabric-0.1.0/src/fabric/adapters/langgraph.py +90 -0
  14. singleaxis_fabric-0.1.0/src/fabric/client.py +215 -0
  15. singleaxis_fabric-0.1.0/src/fabric/decision.py +390 -0
  16. singleaxis_fabric-0.1.0/src/fabric/escalation.py +97 -0
  17. singleaxis_fabric-0.1.0/src/fabric/guardrails.py +80 -0
  18. singleaxis_fabric-0.1.0/src/fabric/memory.py +78 -0
  19. singleaxis_fabric-0.1.0/src/fabric/nemo.py +151 -0
  20. singleaxis_fabric-0.1.0/src/fabric/presidio.py +115 -0
  21. singleaxis_fabric-0.1.0/src/fabric/py.typed +0 -0
  22. singleaxis_fabric-0.1.0/src/fabric/retrieval.py +92 -0
  23. singleaxis_fabric-0.1.0/src/fabric/tracing.py +59 -0
  24. singleaxis_fabric-0.1.0/tests/__init__.py +2 -0
  25. singleaxis_fabric-0.1.0/tests/_fake_sidecar.py +102 -0
  26. singleaxis_fabric-0.1.0/tests/conftest.py +45 -0
  27. singleaxis_fabric-0.1.0/tests/test_adapters_agent_framework.py +162 -0
  28. singleaxis_fabric-0.1.0/tests/test_adapters_crewai.py +142 -0
  29. singleaxis_fabric-0.1.0/tests/test_adapters_langgraph.py +100 -0
  30. singleaxis_fabric-0.1.0/tests/test_client.py +53 -0
  31. singleaxis_fabric-0.1.0/tests/test_decision.py +160 -0
  32. singleaxis_fabric-0.1.0/tests/test_escalation.py +166 -0
  33. singleaxis_fabric-0.1.0/tests/test_guardrail_chain.py +271 -0
  34. singleaxis_fabric-0.1.0/tests/test_memory.py +144 -0
  35. singleaxis_fabric-0.1.0/tests/test_nemo.py +131 -0
  36. singleaxis_fabric-0.1.0/tests/test_presidio.py +94 -0
  37. singleaxis_fabric-0.1.0/tests/test_retrieval.py +176 -0
  38. singleaxis_fabric-0.1.0/tests/test_tracing.py +42 -0
@@ -0,0 +1,108 @@
1
+ # --- Secrets (never commit) ---
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # --- Layer 2 / Layer 3 staging (SingleAxis-internal, never public) ---
7
+ # See _internal/README.md. Moves to a separate private repo per the
8
+ # documented Phase-1 split; excluded here so the public OSS tree
9
+ # stays Layer-1 only.
10
+ /_internal/
11
+ *.pem
12
+ *.key
13
+ *.crt
14
+ *.p12
15
+ *.pfx
16
+ secrets/
17
+ .secrets/
18
+
19
+ # --- Python ---
20
+ __pycache__/
21
+ *.py[cod]
22
+ *$py.class
23
+ *.egg-info/
24
+ .eggs/
25
+ .pytest_cache/
26
+ .mypy_cache/
27
+ .ruff_cache/
28
+ .coverage
29
+ .coverage.*
30
+ coverage.xml
31
+ htmlcov/
32
+ dist/
33
+ build/
34
+ .tox/
35
+ .nox/
36
+ .venv/
37
+ venv/
38
+ ENV/
39
+ env/
40
+ *.whl
41
+
42
+ # --- Go ---
43
+ bin/
44
+ vendor/
45
+ *.test
46
+ *.out
47
+
48
+ # --- Node / JS (docs site, SDK) ---
49
+ node_modules/
50
+ npm-debug.log*
51
+ yarn-debug.log*
52
+ yarn-error.log*
53
+ .pnpm-debug.log*
54
+
55
+ # --- Helm ---
56
+ # Note: first-party subcharts under charts/fabric/charts/<name>/ ARE
57
+ # committed. Only ignore .tgz packages produced by `helm dependency
58
+ # update` and the deprecated lock file.
59
+ charts/*/*.tgz
60
+ charts/*/charts/*.tgz
61
+ charts/*/requirements.lock
62
+
63
+ # --- Terraform ---
64
+ *.tfstate
65
+ *.tfstate.*
66
+ .terraform/
67
+ .terraform.lock.hcl
68
+ crash.log
69
+ *.tfplan
70
+
71
+ # --- Docker ---
72
+ .docker/
73
+
74
+ # --- IDE / Editor ---
75
+ .claude/
76
+ .vscode/
77
+ !.vscode/settings.json.example
78
+ !.vscode/extensions.json
79
+ .idea/
80
+ *.swp
81
+ *.swo
82
+ *~
83
+ .DS_Store
84
+ Thumbs.db
85
+
86
+ # --- OS ---
87
+ *.log
88
+ *.pid
89
+ *.seed
90
+
91
+ # --- Build artefacts ---
92
+ *.tar.gz
93
+ *.zip
94
+ sbom*.json
95
+ sbom*.xml
96
+ *.sig
97
+ *.sigstore
98
+ *.attestation
99
+
100
+ # --- Local development ---
101
+ .local/
102
+ tmp/
103
+ tmp_*/
104
+
105
+ # --- Docs build output ---
106
+ docs/_build/
107
+ docs/.cache/
108
+ site/
@@ -0,0 +1,15 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: singleaxis-fabric
3
+ Version: 0.1.0
4
+ Summary: SingleAxis Fabric SDK for Python — inline guardrails, memory, tracing, escalation helpers.
5
+ Project-URL: Homepage, https://github.com/ai5labs/singleaxis-fabric
6
+ Project-URL: Repository, https://github.com/ai5labs/singleaxis-fabric
7
+ Project-URL: Documentation, https://github.com/ai5labs/singleaxis-fabric/blob/main/specs/002-architecture.md
8
+ Project-URL: Issues, https://github.com/ai5labs/singleaxis-fabric/issues
9
+ Author-email: "AI5 Labs, Inc." <fabric@ai5labs.com>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agent,compliance,fabric,guardrails,llm,otel,singleaxis
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: opentelemetry-api>=1.27
22
+ Requires-Dist: opentelemetry-sdk>=1.27
23
+ Requires-Dist: pydantic<3.0,>=2.8
24
+ Provides-Extra: agent-framework
25
+ Requires-Dist: agent-framework-core<2.0,>=0.1; extra == 'agent-framework'
26
+ Provides-Extra: crewai
27
+ Requires-Dist: crewai<1.0,>=0.80; extra == 'crewai'
28
+ Requires-Dist: litellm>=1.83.0; extra == 'crewai'
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.11.2; extra == 'dev'
31
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.3; extra == 'dev'
34
+ Requires-Dist: ruff>=0.6.9; extra == 'dev'
35
+ Provides-Extra: langgraph
36
+ Requires-Dist: langgraph<1.0,>=0.2; extra == 'langgraph'
37
+ Provides-Extra: otlp
38
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == 'otlp'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # singleaxis-fabric (Python SDK)
42
+
43
+ Native Python SDK that tenant agents import in-process. Provides the
44
+ decision-span contract, guardrail/escalation types, OTel plumbing,
45
+ and optional adapters for LangGraph, Microsoft Agent Framework, and
46
+ CrewAI (installed via extras — the core SDK stays framework-neutral).
47
+
48
+ ## Authoritative specs
49
+
50
+ - [`../../specs/002-architecture.md`](../../specs/002-architecture.md)
51
+ - [`../../specs/005-guardrails-inline.md`](../../specs/005-guardrails-inline.md)
52
+ - [`../../specs/003-context-graph.md`](../../specs/003-context-graph.md) (retrieval audit)
53
+ - [`../../specs/007-escalation-workflow.md`](../../specs/007-escalation-workflow.md) (pause primitive)
54
+
55
+ ## Status
56
+
57
+ Pre-alpha — **Phase 1a shipping.**
58
+
59
+ ### Shipping now
60
+
61
+ - `Fabric` client (`Fabric.from_env`, `FabricConfig`, `close()`)
62
+ - `Decision` context manager — opens an OTel span per agent call and
63
+ tags it with the Fabric-standard attributes:
64
+ - `fabric.tenant_id`, `fabric.agent_id`, `fabric.profile`
65
+ - `fabric.session_id`, `fabric.request_id`, `fabric.user_id`
66
+ - `fabric.blocked`, `fabric.blocked.policies` (on block)
67
+ - Guardrail types: `GuardrailResult`, `EntitySummary`,
68
+ `GuardrailBlocked`, `GuardrailNotConfiguredError`
69
+ - Presidio rail via UDS sidecar: `UDSPresidioClient`,
70
+ `RedactionResult`, `RedactionError`. `Decision.guard_input`,
71
+ `guard_output_chunk`, and `guard_output_final` route through the
72
+ chain and emit `fabric.guardrail` span events (phase, latency_ms,
73
+ blocked, entities, policies).
74
+ - NeMo Colang rail via UDS sidecar: `UDSNemoClient`, `NemoResult`,
75
+ `NemoError`. Wired into the same chain; runs **after** Presidio so
76
+ the Colang / LLM checks never see raw PII. May block
77
+ (`action == "block"`), with the canned response surfaced on the
78
+ `GuardrailResult`.
79
+ - OTel helpers: `get_tracer`, `install_default_provider`
80
+ - Decision-level block recording (`record_block`, `raise_for_block`)
81
+ - Retrieval recording: `RetrievalSource`, `RetrievalRecord`,
82
+ `Decision.record_retrieval(source, query=..., result_count=..., ...)`.
83
+ Hashes the query with SHA-256 locally (raw text never hits the
84
+ span), emits a `fabric.retrieval` span event with allowlisted
85
+ attributes, and maintains rolling `fabric.retrieval_count` and
86
+ `fabric.retrieval_sources` on the decision span so the Telemetry
87
+ Bridge can fold them into the `DecisionSummary` wire event. Maps
88
+ onto the Context Graph's `Retrieval` node (spec 003).
89
+ - Escalation pause primitive: `EscalationSummary`, `EscalationRequested`,
90
+ `Decision.request_escalation`, `Decision.raise_for_escalation`.
91
+ Records `fabric.escalated`, `fabric.escalation.reason/rubric_id/
92
+ mode/triggering_score` on the span and emits a `fabric.escalation`
93
+ span event. `EscalationSummary.to_payload()` returns the
94
+ framework-agnostic dict tenants hand to whatever interrupt
95
+ primitive their orchestrator exposes (LangGraph `interrupt()`,
96
+ Agent Framework checkpoints, a bespoke queue). The SDK owns the
97
+ *local* signal only; the downstream SASF review + signed-verdict
98
+ resume lives in the escalation service (spec 007).
99
+ - Memory write recording: `MemoryKind`, `MemoryRecord`,
100
+ `Decision.remember(kind=..., content=..., key=..., tags=...,
101
+ ttl_seconds=...)`. Tenants perform the actual write against their
102
+ own memory store; the SDK SHA-256s the content locally (raw text
103
+ never hits the span) and emits a `fabric.memory` span event with
104
+ the allowlisted metadata, plus rolling `fabric.memory_write_count`
105
+ and `fabric.memory_kinds` attributes the Telemetry Bridge folds
106
+ into the `DecisionSummary` wire event. Symmetric to
107
+ `record_retrieval` — the Context Graph materializes the write as
108
+ a `Retrieval` node with `source=memory` tied to the owning
109
+ `Decision`.
110
+
111
+ ```python
112
+ from fabric import MemoryKind
113
+
114
+ with fabric.decision(session_id=sess, request_id=req) as decision:
115
+ answer = my_agent.run(user_input)
116
+ my_memory_store.write(key="last_answer", value=answer)
117
+ decision.remember(
118
+ kind=MemoryKind.EPISODIC,
119
+ key="last_answer",
120
+ content=answer,
121
+ tags=("turn", "assistant"),
122
+ )
123
+ ```
124
+
125
+ When no rails are configured, `guard_input` / `guard_output_*` raise
126
+ `GuardrailNotConfiguredError`. This is a deliberate fail-loud
127
+ posture — a silently passing guardrail is a compliance footgun.
128
+
129
+ ### Framework adapters (optional)
130
+
131
+ The core SDK is framework-neutral. Adapters live under
132
+ `fabric.adapters.*` and are each gated behind an install extra so the
133
+ core install does not pull in any orchestration package.
134
+
135
+ - `fabric.adapters.langgraph.escalate(decision, summary)` — records
136
+ the Fabric escalation on the decision span and calls
137
+ `langgraph.types.interrupt(payload)`. Returns whatever the host
138
+ resumes the graph with (typically the signed verdict).
139
+ - `fabric.adapters.agent_framework.request_escalation(ctx, decision,
140
+ summary, *, response_type=...)` — records on span, then
141
+ `await ctx.request_info(request_data=..., response_type=...)`. The
142
+ resumed response is routed to a MAF `@response_handler` method
143
+ (dispatch-based, per MAF design).
144
+ - `fabric.adapters.crewai.attach_callbacks(decision)` returns
145
+ `CrewCallbacks` (step + task callbacks that record CrewAI
146
+ lifecycle events on the decision span).
147
+ `fabric.adapters.crewai.request_escalation(decision, summary)`
148
+ records on span and returns the canonical payload — the tenant
149
+ pairs it with their chosen CrewAI HITL channel (`@human_feedback`
150
+ Flow, `Task(human_input=True)`, or enterprise `/resume`).
151
+
152
+ ## Install
153
+
154
+ ```bash
155
+ pip install singleaxis-fabric # core
156
+ pip install "singleaxis-fabric[otlp]" # + OTLP/HTTP exporter
157
+ pip install "singleaxis-fabric[langgraph]" # + LangGraph adapter
158
+ pip install "singleaxis-fabric[agent-framework]" # + MAF adapter
159
+ pip install "singleaxis-fabric[crewai]" # + CrewAI adapter
160
+ ```
161
+
162
+ ## Quick start
163
+
164
+ ```python
165
+ import os
166
+ from fabric import Fabric, install_default_provider
167
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
168
+
169
+ # Host chooses how to export — typically an OTLP endpoint pointing at
170
+ # the Fabric OTel Collector. install_default_provider is a convenience
171
+ # for small agents; production hosts wire the provider themselves.
172
+ install_default_provider(
173
+ service_name="support-bot",
174
+ exporter=OTLPSpanExporter(endpoint=os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]),
175
+ )
176
+
177
+ fabric = Fabric.from_env()
178
+
179
+ with fabric.decision(
180
+ session_id=session.id,
181
+ request_id=req.id,
182
+ user_id=user.id,
183
+ ) as decision:
184
+ # real work happens here; the decision span wraps it all
185
+ safe_input = decision.guard_input(req.body)
186
+ output = llm.complete(prompt=safe_input)
187
+ final = decision.guard_output_final(output)
188
+ decision.set_attribute("llm.model", "claude-opus-4-7")
189
+ ```
190
+
191
+ `guard_input` / `guard_output_*` are no-ops that raise
192
+ `GuardrailNotConfiguredError` unless `FABRIC_PRESIDIO_UNIX_SOCKET` is
193
+ set (or a `PresidioClient` is passed to `Fabric(...)` directly).
194
+
195
+ ## Environment variables
196
+
197
+ | Variable | Required | Purpose |
198
+ | --- | --- | --- |
199
+ | `FABRIC_TENANT_ID` | yes | Tenant scope for all emitted events. |
200
+ | `FABRIC_AGENT_ID` | yes | Which agent in the tenant is running. |
201
+ | `FABRIC_PROFILE` | no | Regulatory profile (default `permissive-dev`). |
202
+ | `FABRIC_PRESIDIO_UNIX_SOCKET` | no | Unix socket path to the Presidio sidecar (`/v1/redact`). If unset, the Presidio rail is not installed. |
203
+ | `FABRIC_PRESIDIO_TIMEOUT_SECONDS` | no | Per-call timeout for the sidecar (float, default `0.5`). |
204
+ | `FABRIC_NEMO_UNIX_SOCKET` | no | Unix socket path to the NeMo Colang sidecar (`/v1/check`). If unset, the NeMo rail is not installed. |
205
+ | `FABRIC_NEMO_TIMEOUT_SECONDS` | no | Per-call timeout for the NeMo sidecar (float, default `1.0`). |
206
+
207
+ ## Module layout
208
+
209
+ ```
210
+ sdk/python/
211
+ ├── pyproject.toml
212
+ ├── src/fabric/
213
+ │ ├── __init__.py
214
+ │ ├── client.py # Fabric, FabricConfig, from_env
215
+ │ ├── decision.py # Decision context manager
216
+ │ ├── guardrails.py # result + error types
217
+ │ ├── escalation.py # EscalationSummary + EscalationRequested
218
+ │ ├── presidio.py # PresidioClient protocol + UDS impl
219
+ │ ├── nemo.py # NemoClient protocol + UDS impl
220
+ │ ├── retrieval.py # RetrievalSource + RetrievalRecord
221
+ │ ├── _chain.py # GuardrailChain (internal)
222
+ │ ├── _uds.py # HTTP-over-unix-socket transport
223
+ │ ├── tracing.py # OTel helpers
224
+ │ └── py.typed
225
+ └── tests/
226
+ ├── conftest.py
227
+ ├── _fake_sidecar.py
228
+ ├── test_client.py
229
+ ├── test_decision.py
230
+ ├── test_guardrail_chain.py
231
+ ├── test_escalation.py
232
+ ├── test_nemo.py
233
+ ├── test_presidio.py
234
+ ├── test_retrieval.py
235
+ └── test_tracing.py
236
+ ```
237
+
238
+ ## Tests
239
+
240
+ ```bash
241
+ python -m venv .venv && . .venv/bin/activate
242
+ pip install -e '.[dev]'
243
+ pytest
244
+ ```
245
+
246
+ Coverage threshold is 85% at the pyproject level. Current baseline is
247
+ ~98% because the Phase 1a surface is narrow; as guardrails and memory
248
+ land, keep the 85% floor honest rather than moving it.
249
+
250
+ ## Versioning
251
+
252
+ Independent of the Fabric umbrella version pre-1.0.0. Tenant agents
253
+ pin the SDK; the Control Plane advertises compatibility ranges.
254
+
255
+ ## License
256
+
257
+ Apache-2.0. See `LICENSE`.
@@ -0,0 +1,217 @@
1
+ # singleaxis-fabric (Python SDK)
2
+
3
+ Native Python SDK that tenant agents import in-process. Provides the
4
+ decision-span contract, guardrail/escalation types, OTel plumbing,
5
+ and optional adapters for LangGraph, Microsoft Agent Framework, and
6
+ CrewAI (installed via extras — the core SDK stays framework-neutral).
7
+
8
+ ## Authoritative specs
9
+
10
+ - [`../../specs/002-architecture.md`](../../specs/002-architecture.md)
11
+ - [`../../specs/005-guardrails-inline.md`](../../specs/005-guardrails-inline.md)
12
+ - [`../../specs/003-context-graph.md`](../../specs/003-context-graph.md) (retrieval audit)
13
+ - [`../../specs/007-escalation-workflow.md`](../../specs/007-escalation-workflow.md) (pause primitive)
14
+
15
+ ## Status
16
+
17
+ Pre-alpha — **Phase 1a shipping.**
18
+
19
+ ### Shipping now
20
+
21
+ - `Fabric` client (`Fabric.from_env`, `FabricConfig`, `close()`)
22
+ - `Decision` context manager — opens an OTel span per agent call and
23
+ tags it with the Fabric-standard attributes:
24
+ - `fabric.tenant_id`, `fabric.agent_id`, `fabric.profile`
25
+ - `fabric.session_id`, `fabric.request_id`, `fabric.user_id`
26
+ - `fabric.blocked`, `fabric.blocked.policies` (on block)
27
+ - Guardrail types: `GuardrailResult`, `EntitySummary`,
28
+ `GuardrailBlocked`, `GuardrailNotConfiguredError`
29
+ - Presidio rail via UDS sidecar: `UDSPresidioClient`,
30
+ `RedactionResult`, `RedactionError`. `Decision.guard_input`,
31
+ `guard_output_chunk`, and `guard_output_final` route through the
32
+ chain and emit `fabric.guardrail` span events (phase, latency_ms,
33
+ blocked, entities, policies).
34
+ - NeMo Colang rail via UDS sidecar: `UDSNemoClient`, `NemoResult`,
35
+ `NemoError`. Wired into the same chain; runs **after** Presidio so
36
+ the Colang / LLM checks never see raw PII. May block
37
+ (`action == "block"`), with the canned response surfaced on the
38
+ `GuardrailResult`.
39
+ - OTel helpers: `get_tracer`, `install_default_provider`
40
+ - Decision-level block recording (`record_block`, `raise_for_block`)
41
+ - Retrieval recording: `RetrievalSource`, `RetrievalRecord`,
42
+ `Decision.record_retrieval(source, query=..., result_count=..., ...)`.
43
+ Hashes the query with SHA-256 locally (raw text never hits the
44
+ span), emits a `fabric.retrieval` span event with allowlisted
45
+ attributes, and maintains rolling `fabric.retrieval_count` and
46
+ `fabric.retrieval_sources` on the decision span so the Telemetry
47
+ Bridge can fold them into the `DecisionSummary` wire event. Maps
48
+ onto the Context Graph's `Retrieval` node (spec 003).
49
+ - Escalation pause primitive: `EscalationSummary`, `EscalationRequested`,
50
+ `Decision.request_escalation`, `Decision.raise_for_escalation`.
51
+ Records `fabric.escalated`, `fabric.escalation.reason/rubric_id/
52
+ mode/triggering_score` on the span and emits a `fabric.escalation`
53
+ span event. `EscalationSummary.to_payload()` returns the
54
+ framework-agnostic dict tenants hand to whatever interrupt
55
+ primitive their orchestrator exposes (LangGraph `interrupt()`,
56
+ Agent Framework checkpoints, a bespoke queue). The SDK owns the
57
+ *local* signal only; the downstream SASF review + signed-verdict
58
+ resume lives in the escalation service (spec 007).
59
+ - Memory write recording: `MemoryKind`, `MemoryRecord`,
60
+ `Decision.remember(kind=..., content=..., key=..., tags=...,
61
+ ttl_seconds=...)`. Tenants perform the actual write against their
62
+ own memory store; the SDK SHA-256s the content locally (raw text
63
+ never hits the span) and emits a `fabric.memory` span event with
64
+ the allowlisted metadata, plus rolling `fabric.memory_write_count`
65
+ and `fabric.memory_kinds` attributes the Telemetry Bridge folds
66
+ into the `DecisionSummary` wire event. Symmetric to
67
+ `record_retrieval` — the Context Graph materializes the write as
68
+ a `Retrieval` node with `source=memory` tied to the owning
69
+ `Decision`.
70
+
71
+ ```python
72
+ from fabric import MemoryKind
73
+
74
+ with fabric.decision(session_id=sess, request_id=req) as decision:
75
+ answer = my_agent.run(user_input)
76
+ my_memory_store.write(key="last_answer", value=answer)
77
+ decision.remember(
78
+ kind=MemoryKind.EPISODIC,
79
+ key="last_answer",
80
+ content=answer,
81
+ tags=("turn", "assistant"),
82
+ )
83
+ ```
84
+
85
+ When no rails are configured, `guard_input` / `guard_output_*` raise
86
+ `GuardrailNotConfiguredError`. This is a deliberate fail-loud
87
+ posture — a silently passing guardrail is a compliance footgun.
88
+
89
+ ### Framework adapters (optional)
90
+
91
+ The core SDK is framework-neutral. Adapters live under
92
+ `fabric.adapters.*` and are each gated behind an install extra so the
93
+ core install does not pull in any orchestration package.
94
+
95
+ - `fabric.adapters.langgraph.escalate(decision, summary)` — records
96
+ the Fabric escalation on the decision span and calls
97
+ `langgraph.types.interrupt(payload)`. Returns whatever the host
98
+ resumes the graph with (typically the signed verdict).
99
+ - `fabric.adapters.agent_framework.request_escalation(ctx, decision,
100
+ summary, *, response_type=...)` — records on span, then
101
+ `await ctx.request_info(request_data=..., response_type=...)`. The
102
+ resumed response is routed to a MAF `@response_handler` method
103
+ (dispatch-based, per MAF design).
104
+ - `fabric.adapters.crewai.attach_callbacks(decision)` returns
105
+ `CrewCallbacks` (step + task callbacks that record CrewAI
106
+ lifecycle events on the decision span).
107
+ `fabric.adapters.crewai.request_escalation(decision, summary)`
108
+ records on span and returns the canonical payload — the tenant
109
+ pairs it with their chosen CrewAI HITL channel (`@human_feedback`
110
+ Flow, `Task(human_input=True)`, or enterprise `/resume`).
111
+
112
+ ## Install
113
+
114
+ ```bash
115
+ pip install singleaxis-fabric # core
116
+ pip install "singleaxis-fabric[otlp]" # + OTLP/HTTP exporter
117
+ pip install "singleaxis-fabric[langgraph]" # + LangGraph adapter
118
+ pip install "singleaxis-fabric[agent-framework]" # + MAF adapter
119
+ pip install "singleaxis-fabric[crewai]" # + CrewAI adapter
120
+ ```
121
+
122
+ ## Quick start
123
+
124
+ ```python
125
+ import os
126
+ from fabric import Fabric, install_default_provider
127
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
128
+
129
+ # Host chooses how to export — typically an OTLP endpoint pointing at
130
+ # the Fabric OTel Collector. install_default_provider is a convenience
131
+ # for small agents; production hosts wire the provider themselves.
132
+ install_default_provider(
133
+ service_name="support-bot",
134
+ exporter=OTLPSpanExporter(endpoint=os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]),
135
+ )
136
+
137
+ fabric = Fabric.from_env()
138
+
139
+ with fabric.decision(
140
+ session_id=session.id,
141
+ request_id=req.id,
142
+ user_id=user.id,
143
+ ) as decision:
144
+ # real work happens here; the decision span wraps it all
145
+ safe_input = decision.guard_input(req.body)
146
+ output = llm.complete(prompt=safe_input)
147
+ final = decision.guard_output_final(output)
148
+ decision.set_attribute("llm.model", "claude-opus-4-7")
149
+ ```
150
+
151
+ `guard_input` / `guard_output_*` are no-ops that raise
152
+ `GuardrailNotConfiguredError` unless `FABRIC_PRESIDIO_UNIX_SOCKET` is
153
+ set (or a `PresidioClient` is passed to `Fabric(...)` directly).
154
+
155
+ ## Environment variables
156
+
157
+ | Variable | Required | Purpose |
158
+ | --- | --- | --- |
159
+ | `FABRIC_TENANT_ID` | yes | Tenant scope for all emitted events. |
160
+ | `FABRIC_AGENT_ID` | yes | Which agent in the tenant is running. |
161
+ | `FABRIC_PROFILE` | no | Regulatory profile (default `permissive-dev`). |
162
+ | `FABRIC_PRESIDIO_UNIX_SOCKET` | no | Unix socket path to the Presidio sidecar (`/v1/redact`). If unset, the Presidio rail is not installed. |
163
+ | `FABRIC_PRESIDIO_TIMEOUT_SECONDS` | no | Per-call timeout for the sidecar (float, default `0.5`). |
164
+ | `FABRIC_NEMO_UNIX_SOCKET` | no | Unix socket path to the NeMo Colang sidecar (`/v1/check`). If unset, the NeMo rail is not installed. |
165
+ | `FABRIC_NEMO_TIMEOUT_SECONDS` | no | Per-call timeout for the NeMo sidecar (float, default `1.0`). |
166
+
167
+ ## Module layout
168
+
169
+ ```
170
+ sdk/python/
171
+ ├── pyproject.toml
172
+ ├── src/fabric/
173
+ │ ├── __init__.py
174
+ │ ├── client.py # Fabric, FabricConfig, from_env
175
+ │ ├── decision.py # Decision context manager
176
+ │ ├── guardrails.py # result + error types
177
+ │ ├── escalation.py # EscalationSummary + EscalationRequested
178
+ │ ├── presidio.py # PresidioClient protocol + UDS impl
179
+ │ ├── nemo.py # NemoClient protocol + UDS impl
180
+ │ ├── retrieval.py # RetrievalSource + RetrievalRecord
181
+ │ ├── _chain.py # GuardrailChain (internal)
182
+ │ ├── _uds.py # HTTP-over-unix-socket transport
183
+ │ ├── tracing.py # OTel helpers
184
+ │ └── py.typed
185
+ └── tests/
186
+ ├── conftest.py
187
+ ├── _fake_sidecar.py
188
+ ├── test_client.py
189
+ ├── test_decision.py
190
+ ├── test_guardrail_chain.py
191
+ ├── test_escalation.py
192
+ ├── test_nemo.py
193
+ ├── test_presidio.py
194
+ ├── test_retrieval.py
195
+ └── test_tracing.py
196
+ ```
197
+
198
+ ## Tests
199
+
200
+ ```bash
201
+ python -m venv .venv && . .venv/bin/activate
202
+ pip install -e '.[dev]'
203
+ pytest
204
+ ```
205
+
206
+ Coverage threshold is 85% at the pyproject level. Current baseline is
207
+ ~98% because the Phase 1a surface is narrow; as guardrails and memory
208
+ land, keep the 85% floor honest rather than moving it.
209
+
210
+ ## Versioning
211
+
212
+ Independent of the Fabric umbrella version pre-1.0.0. Tenant agents
213
+ pin the SDK; the Control Plane advertises compatibility ranges.
214
+
215
+ ## License
216
+
217
+ Apache-2.0. See `LICENSE`.