thread.step() calls to application code.
The Mapping Model
| 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
| 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. |
UNSET is treated as success because OpenTelemetry uses it when no error was recorded.1. Setup the Exporter
Create the exporter and register it with your OpenTelemetryTracerProvider.
- Node.js
- Go
- Python
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
Ended spans are exported automatically; the exporter manages thread lifecycles.
2. Filtering Spans
Usefilters to drop internal or utility spans by name before export.
| 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, instrument business logic with the standard OpenTelemetry API.- Node.js
- Go
- Python
// 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)
Usethreadify.* span attributes to override the default mapping.
| 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 |
threadify.ref.* or threadify.context.* will be explicitly mapped to references or context respectively, bypassing the default configuration rules.