SingleAxis Fabric SDK for Python — inline guardrails, memory, tracing, escalation helpers.
Project description
singleaxis-fabric (Python SDK)
Native Python SDK that tenant agents import in-process. Provides the decision-span contract, guardrail/escalation types, OTel plumbing, and optional adapters for LangGraph, Microsoft Agent Framework, and CrewAI (installed via extras — the core SDK stays framework-neutral).
Authoritative specs
../../specs/002-architecture.md../../specs/005-guardrails-inline.md../../specs/003-context-graph.md(retrieval audit)../../specs/007-escalation-workflow.md(pause primitive)
Status
Pre-alpha — Phase 1a shipping.
Shipping now
-
Fabricclient (Fabric.from_env,FabricConfig,close()) -
Decisioncontext manager — opens an OTel span per agent call and tags it with the Fabric-standard attributes:fabric.tenant_id,fabric.agent_id,fabric.profilefabric.session_id,fabric.request_id,fabric.user_idfabric.blocked,fabric.blocked.policies(on block)
-
Guardrail types:
GuardrailResult,EntitySummary,GuardrailBlocked,GuardrailNotConfiguredError -
Presidio rail via UDS sidecar:
UDSPresidioClient,RedactionResult,RedactionError.Decision.guard_input,guard_output_chunk, andguard_output_finalroute through the chain and emitfabric.guardrailspan events (phase, latency_ms, blocked, entities, policies). -
NeMo Colang rail via UDS sidecar:
UDSNemoClient,NemoResult,NemoError. Wired into the same chain; runs after Presidio so the Colang / LLM checks never see raw PII. May block (action == "block"), with the canned response surfaced on theGuardrailResult. -
OTel helpers:
get_tracer,install_default_provider -
Decision-level block recording (
record_block,raise_for_block) -
Retrieval recording:
RetrievalSource,RetrievalRecord,Decision.record_retrieval(source, query=..., result_count=..., ...). Hashes the query with SHA-256 locally (raw text never hits the span), emits afabric.retrievalspan event with allowlisted attributes, and maintains rollingfabric.retrieval_countandfabric.retrieval_sourceson the decision span so the Telemetry Bridge can fold them into theDecisionSummarywire event. Maps onto the Context Graph'sRetrievalnode (spec 003). -
Escalation pause primitive:
EscalationSummary,EscalationRequested,Decision.request_escalation,Decision.raise_for_escalation. Recordsfabric.escalated,fabric.escalation.reason/rubric_id/ mode/triggering_scoreon the span and emits afabric.escalationspan event.EscalationSummary.to_payload()returns the framework-agnostic dict tenants hand to whatever interrupt primitive their orchestrator exposes (LangGraphinterrupt(), Agent Framework checkpoints, a bespoke queue). The SDK owns the local signal only; the downstream SASF review + signed-verdict resume lives in the escalation service (spec 007). -
Memory write recording:
MemoryKind,MemoryRecord,Decision.remember(kind=..., content=..., key=..., tags=..., ttl_seconds=...). Tenants perform the actual write against their own memory store; the SDK SHA-256s the content locally (raw text never hits the span) and emits afabric.memoryspan event with the allowlisted metadata, plus rollingfabric.memory_write_countandfabric.memory_kindsattributes the Telemetry Bridge folds into theDecisionSummarywire event. Symmetric torecord_retrieval— the Context Graph materializes the write as aRetrievalnode withsource=memorytied to the owningDecision.from fabric import MemoryKind with fabric.decision(session_id=sess, request_id=req) as decision: answer = my_agent.run(user_input) my_memory_store.write(key="last_answer", value=answer) decision.remember( kind=MemoryKind.EPISODIC, key="last_answer", content=answer, tags=("turn", "assistant"), )
When no rails are configured, guard_input / guard_output_* raise
GuardrailNotConfiguredError. This is a deliberate fail-loud
posture — a silently passing guardrail is a compliance footgun.
Framework adapters (optional)
The core SDK is framework-neutral. Adapters live under
fabric.adapters.* and are each gated behind an install extra so the
core install does not pull in any orchestration package.
fabric.adapters.langgraph.escalate(decision, summary)— records the Fabric escalation on the decision span and callslanggraph.types.interrupt(payload). Returns whatever the host resumes the graph with (typically the signed verdict).fabric.adapters.agent_framework.request_escalation(ctx, decision, summary, *, response_type=...)— records on span, thenawait ctx.request_info(request_data=..., response_type=...). The resumed response is routed to a MAF@response_handlermethod (dispatch-based, per MAF design).fabric.adapters.crewai.attach_callbacks(decision)returnsCrewCallbacks(step + task callbacks that record CrewAI lifecycle events on the decision span).fabric.adapters.crewai.request_escalation(decision, summary)records on span and returns the canonical payload — the tenant pairs it with their chosen CrewAI HITL channel (@human_feedbackFlow,Task(human_input=True), or enterprise/resume).
Install
pip install singleaxis-fabric # core
pip install "singleaxis-fabric[otlp]" # + OTLP/HTTP exporter
pip install "singleaxis-fabric[langgraph]" # + LangGraph adapter
pip install "singleaxis-fabric[agent-framework]" # + MAF adapter
pip install "singleaxis-fabric[crewai]" # + CrewAI adapter
Quick start
import os
from fabric import Fabric, install_default_provider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Host chooses how to export — typically an OTLP endpoint pointing at
# the Fabric OTel Collector. install_default_provider is a convenience
# for small agents; production hosts wire the provider themselves.
install_default_provider(
service_name="support-bot",
exporter=OTLPSpanExporter(endpoint=os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]),
)
fabric = Fabric.from_env()
with fabric.decision(
session_id=session.id,
request_id=req.id,
user_id=user.id,
) as decision:
# real work happens here; the decision span wraps it all
safe_input = decision.guard_input(req.body)
output = llm.complete(prompt=safe_input)
final = decision.guard_output_final(output)
decision.set_attribute("llm.model", "claude-opus-4-7")
guard_input / guard_output_* are no-ops that raise
GuardrailNotConfiguredError unless FABRIC_PRESIDIO_UNIX_SOCKET is
set (or a PresidioClient is passed to Fabric(...) directly).
Environment variables
| Variable | Required | Purpose |
|---|---|---|
FABRIC_TENANT_ID |
yes | Tenant scope for all emitted events. |
FABRIC_AGENT_ID |
yes | Which agent in the tenant is running. |
FABRIC_PROFILE |
no | Regulatory profile (default permissive-dev). |
FABRIC_PRESIDIO_UNIX_SOCKET |
no | Unix socket path to the Presidio sidecar (/v1/redact). If unset, the Presidio rail is not installed. |
FABRIC_PRESIDIO_TIMEOUT_SECONDS |
no | Per-call timeout for the sidecar (float, default 0.5). |
FABRIC_NEMO_UNIX_SOCKET |
no | Unix socket path to the NeMo Colang sidecar (/v1/check). If unset, the NeMo rail is not installed. |
FABRIC_NEMO_TIMEOUT_SECONDS |
no | Per-call timeout for the NeMo sidecar (float, default 1.0). |
Module layout
sdk/python/
├── pyproject.toml
├── src/fabric/
│ ├── __init__.py
│ ├── client.py # Fabric, FabricConfig, from_env
│ ├── decision.py # Decision context manager
│ ├── guardrails.py # result + error types
│ ├── escalation.py # EscalationSummary + EscalationRequested
│ ├── presidio.py # PresidioClient protocol + UDS impl
│ ├── nemo.py # NemoClient protocol + UDS impl
│ ├── retrieval.py # RetrievalSource + RetrievalRecord
│ ├── _chain.py # GuardrailChain (internal)
│ ├── _uds.py # HTTP-over-unix-socket transport
│ ├── tracing.py # OTel helpers
│ └── py.typed
└── tests/
├── conftest.py
├── _fake_sidecar.py
├── test_client.py
├── test_decision.py
├── test_guardrail_chain.py
├── test_escalation.py
├── test_nemo.py
├── test_presidio.py
├── test_retrieval.py
└── test_tracing.py
Tests
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytest
Coverage threshold is 85% at the pyproject level. Current baseline is ~98% because the Phase 1a surface is narrow; as guardrails and memory land, keep the 85% floor honest rather than moving it.
Versioning
Independent of the Fabric umbrella version pre-1.0.0. Tenant agents pin the SDK; the Control Plane advertises compatibility ranges.
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file singleaxis_fabric-0.1.0rc4.tar.gz.
File metadata
- Download URL: singleaxis_fabric-0.1.0rc4.tar.gz
- Upload date:
- Size: 37.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ceb5a76e0853a852153b0db27dbb5e8089c14179b66b2b8fd429a7cf9763cc2
|
|
| MD5 |
b3b3b3908442f7ecb91067b19da952f0
|
|
| BLAKE2b-256 |
00ef25fa008dfa3b201fb668e89d64669d8e3737738fb3f7663411f689f204e2
|
Provenance
The following attestation bundles were made for singleaxis_fabric-0.1.0rc4.tar.gz:
Publisher:
release.yml on singleaxis/singleaxis-fabric
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
singleaxis_fabric-0.1.0rc4.tar.gz -
Subject digest:
0ceb5a76e0853a852153b0db27dbb5e8089c14179b66b2b8fd429a7cf9763cc2 - Sigstore transparency entry: 1368269338
- Sigstore integration time:
-
Permalink:
singleaxis/singleaxis-fabric@8f4a9073bc3302b1bc102dde64a3ac9bd54ec015 -
Branch / Tag:
refs/tags/v0.1.0-rc.4 - Owner: https://github.com/singleaxis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f4a9073bc3302b1bc102dde64a3ac9bd54ec015 -
Trigger Event:
push
-
Statement type:
File details
Details for the file singleaxis_fabric-0.1.0rc4-py3-none-any.whl.
File metadata
- Download URL: singleaxis_fabric-0.1.0rc4-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e678cdaeb757546745c238d9c216509c96dadb485d3b73ed09d8b891bed8773
|
|
| MD5 |
bf736aa538aaf3e2bd3b4c47f27fa6b3
|
|
| BLAKE2b-256 |
734f397e5d35d5e219d10f0b5f01da80dfe39370c44b1850c7c0ae763cd58e6d
|
Provenance
The following attestation bundles were made for singleaxis_fabric-0.1.0rc4-py3-none-any.whl:
Publisher:
release.yml on singleaxis/singleaxis-fabric
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
singleaxis_fabric-0.1.0rc4-py3-none-any.whl -
Subject digest:
2e678cdaeb757546745c238d9c216509c96dadb485d3b73ed09d8b891bed8773 - Sigstore transparency entry: 1368269344
- Sigstore integration time:
-
Permalink:
singleaxis/singleaxis-fabric@8f4a9073bc3302b1bc102dde64a3ac9bd54ec015 -
Branch / Tag:
refs/tags/v0.1.0-rc.4 - Owner: https://github.com/singleaxis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f4a9073bc3302b1bc102dde64a3ac9bd54ec015 -
Trigger Event:
push
-
Statement type: