Threadify is a service-delivery intelligence system. The Threadify SDK supports converting standard OpenTelemetry (OTel) traces into Threadify’s service-delivery concepts, thereby providing deep, actionable intelligence into exactly how you are delivering to your customers.
If your application is already instrumented with OpenTelemetry (OTel), you do not need to manually add thread.step() calls throughout your codebase. Instead, you can use the Threadify OpenTelemetry Exporter to seamlessly translate your existing traces into Threadify execution graphs.
The Mapping Model
The Threadify Exporter automatically translates the highly technical OTel hierarchy into business-friendly language:
| OpenTelemetry Concept | Threadify Concept | Description |
|---|
| Trace | Thread | A Trace represents an entire business workflow (e.g., Customer Checkout). |
| Span | Step | A Span represents a major operation (e.g., process_payment). |
| Span Event | Sub-Step | An Event is a specific milestone during the operation (e.g., validated_card). |
| Span Link | Link | Coming Soon. |
Status Mapping
The Exporter automatically translates the OpenTelemetry SpanStatus into Threadify step outcomes. This ensures that technical failures (and even ambiguous states) are correctly captured as service-delivery intelligence.
| Span Status | Threadify Outcome | Rationale |
|---|
SpanStatus.OK | success | Explicitly marked as successful by the developer. |
SpanStatus.ERROR | failed | Explicitly marked as a failure or exception. |
SpanStatus.UNSET | success | The default status. Threadify treats “no error” as a successful delivery. |
By following the standard OpenTelemetry convention where UNSET implies success, Threadify ensures zero-friction integration for existing instrumented applications.
1. Setup the Exporter
Integrating the exporter into your application takes just a few lines of code. You initialize the Exporter and register it with your global OpenTelemetry TracerProvider.
import { Threadify } from '@threadify/sdk';
import { trace } from '@opentelemetry/api';
import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
// 1. Establish your Threadify Connection
const connection = await Threadify.connect('your-api-key', 'your-service');
// 2. Create the Threadify Exporter
// Map span attributes to Threadify "Refs" using custom ref keys
const exporter = connection.createSpanExporter({
refs: { orderId: 'order.id', customerId: 'customer.id' }
});
// Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
const exporter = connection.createSpanExporter({
refs: { orderId: 'order.id', customerId: 'customer.id' },
filters: ['invoke_llm', 'adk.before*', 'llm.*']
});
// 3. Attach the Exporter to OpenTelemetry
const provider = new BasicTracerProvider({
spanProcessors: [new SimpleSpanProcessor(exporter)]
});
trace.setGlobalTracerProvider(provider);
Because Go is statically typed, the OpenTelemetry integration is provided via a separate sub-module so the core Threadify SDK remains lightweight.go get github.com/ThreadifyDev/go-sdk/otel
import (
"context"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
threadify "github.com/ThreadifyDev/go-sdk"
threadifyotel "github.com/ThreadifyDev/go-sdk/otel"
)
func main() {
ctx := context.Background()
// 1. Establish your Threadify Connection
conn, _ := threadify.Connect(ctx, "your-api-key",
threadify.WithServiceName("my-service"),
)
defer conn.Close()
// 2. Create the Threadify Exporter
exporter := threadifyotel.NewSpanExporter(conn, threadifyotel.SpanExporterOptions{
Refs: map[string]string{"orderId": "order.id", "customerId": "customer.id"},
})
// Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
exporter = threadifyotel.NewSpanExporter(conn, threadifyotel.SpanExporterOptions{
Refs: map[string]string{"orderId": "order.id", "customerId": "customer.id"},
Filters: []string{"invoke_llm", "adk.before*", "llm.*"},
})
// 3. Attach the Exporter to OpenTelemetry
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(provider)
defer provider.Shutdown(ctx)
// ... your application code
}
The Python SDK includes the OpenTelemetry SpanExporter in the core package.pip install opentelemetry-api opentelemetry-sdk
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from threadify import Threadify
async def main():
# 1. Establish your Threadify Connection
conn = await Threadify.connect(
"your-api-key",
service_name="my-service",
)
# 2. Create the Threadify Exporter
exporter = conn.create_span_exporter(
options={"refs": {"orderId": "order.id", "customerId": "customer.id"}}
)
# Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
exporter = conn.create_span_exporter(
options={
"refs": {"orderId": "order.id", "customerId": "customer.id"},
"filters": ["invoke_llm", "adk.before*", "llm.*"],
}
)
# 3. Attach the Exporter to OpenTelemetry
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
# ... your application code
By default, the Exporter will automatically push Spans to Threadify whenever they end. It manages the underlying Threadify Thread lifecycles for you transparently.
2. Filtering Spans
Some applications generate a high volume of internal or utility spans that you may not want to send to Threadify. Use the filters (Node.js/Go) or "filters" (Python) option to drop spans by name before they are exported.
| Filter Pattern | Behavior | Example Match |
|---|
"invoke_llm" | Exact match | invoke_llm |
"adk.before*" | Prefix wildcard (*) | adk.before_tool_call, adk.before |
"llm.*" | Prefix wildcard (*) | llm.chat_completion, llm. |
Filters are applied on the span name only. Trailing * acts as a prefix wildcard. Empty filters are skipped.
3. Using OpenTelemetry Natively
Once configured, you simply use the standard OpenTelemetry API. You do not need to import or call Threadify directly in your business logic!
// Get your standard OTel tracer
const tracer = trace.getTracer('my-service-tracer');
// Start a span (This automatically ensures a Thread exists in Threadify)
const span = tracer.startSpan('process_payment');
// Set explicit Threadify mapping overrides (Optional)
// This tells Threadify to validate this thread against the 'payment_workflow' contract
span.setAttribute('threadify.contract', 'payment_workflow');
// Tag the thread for filtering and categorization
span.setAttribute('threadify.tags', ['production', 'v2.1']);
// Set attributes
span.setAttribute('order.id', 'ORD-98765'); // Extracted as a Ref (based on setup)
span.setAttribute('payment.amount', 42.50); // Extracted as standard Context
// Add Span Events (These automatically become Threadify Sub-Steps!)
span.addEvent('validated_credit_card', {
'card.type': 'visa',
'fraud.score': 0.05
});
// Simulate work...
await processPayment();
// End the span (This triggers the Exporter and pushes the Step to Threadify)
span.end();
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Get your standard OTel tracer
tracer := otel.Tracer("my-service-tracer")
// Start a span (This automatically ensures a Thread exists in Threadify)
ctx, span := tracer.Start(context.Background(), "process_payment")
// Set explicit Threadify mapping overrides (Optional)
// This tells Threadify to validate this thread against the 'payment_workflow' contract
span.SetAttributes(attribute.String("threadify.contract", "payment_workflow"))
// Tag the thread for filtering and categorization
span.SetAttributes(attribute.StringSlice("threadify.tags", []string{"production", "v2.1"}))
// Set attributes
span.SetAttributes(attribute.String("order.id", "ORD-98765")) // Extracted as a Ref (based on setup)
span.SetAttributes(attribute.Float64("payment.amount", 42.50)) // Extracted as standard Context
// Add Span Events (These automatically become Threadify Sub-Steps!)
span.AddEvent("validated_credit_card", trace.WithAttributes(
attribute.String("card.type", "visa"),
attribute.Float64("fraud.score", 0.05),
))
// Simulate work...
processPayment(ctx)
// End the span (This triggers the Exporter and pushes the Step to Threadify)
span.End()
from opentelemetry import trace
# Get your standard OTel tracer
tracer = trace.get_tracer("my-service-tracer")
# Start a span (This automatically ensures a Thread exists in Threadify)
with tracer.start_as_current_span("process_payment") as span:
# Set explicit Threadify mapping overrides (Optional)
# This tells Threadify to validate this thread against the 'payment_workflow' contract
span.set_attribute("threadify.contract", "payment_workflow")
# Tag the thread for filtering and categorization
span.set_attribute("threadify.tags", ["production", "v2.1"])
# Set attributes
span.set_attribute("order.id", "ORD-98765") # Extracted as a Ref (based on setup)
span.set_attribute("payment.amount", 42.50) # Extracted as standard Context
# Add Span Events (These automatically become Threadify Sub-Steps!)
span.add_event("validated_credit_card", {
"card.type": "visa",
"fraud.score": 0.05,
})
# Simulate work...
process_payment()
# Span ends automatically (This triggers the Exporter)
4. Advanced Attributes (Optional)
If you want finer control over how a specific Span maps to Threadify, you can set the following special threadify.* attributes on your Span. If omitted, the Exporter will use sensible defaults.
| Attribute | Description | Default Fallback |
|---|
threadify.contract | The Contract Name to validate this workflow against. | null (Schema-less Thread) |
threadify.step_name | Override the Step name in Threadify. | Uses span.name |
threadify.thread_id | Force the Span to attach to a specific existing Thread ID. | Auto-generates a new Thread per Trace |
threadify.label | The human-readable label for the Thread. | Uses span.name |
threadify.service | Override the service name that executed this step. | Uses the connection’s default service |
threadify.tags | Immutable labels attached to the thread at creation time (e.g., production, v2.1). | null |
Any attributes starting with threadify.ref.* or threadify.context.* will be explicitly mapped to references or context respectively, bypassing the default configuration rules.