SDK Contract Specification
Version: 0.1.0 Status: Draft
Overview
The OpenWorkflow SDK contract defines language-agnostic interfaces and behaviors that all SDK implementations must provide. This specification focuses on the contract, not implementation details.
What this defines:
- Core interfaces (connector registration, workflow execution, backend registration)
- Error taxonomy and handling requirements
- Context shape and data access patterns
- Cancellation and timeout semantics
- Tracing hooks and telemetry requirements
What this does NOT define:
- Language-specific API design (defer to idiomatic patterns)
- Implementation algorithms (e.g., retry logic internals)
- Framework integrations (FastAPI, Express, etc. are optional)
Design Principles
- Minimal dependencies: SDKs should be lightweight
- Idiomatic code: Follow language-specific conventions
- Framework agnostic: Work with any web framework or runtime
- Progressive enhancement: Start simple, add features as needed
Core SDK Components
1. Connector Registration
SDKs must provide a way to register connectors from files or objects:
Python:
from openworkflow import OpenWorkflow
# From file
openworkflow = OpenWorkflow()
openworkflow.register_connector("./connectors/weather.yaml")
# From object
connector_config = {
"connector": {
"type": "connector",
"kind": "http",
"name": "weather",
"namespace": "smartify",
"version": "1.0.0"
},
"actions": [...]
}
openworkflow.register_connector(connector_config)
JavaScript:
import { OpenWorkflow } from '@openworkflow/sdk';
const openworkflow = new OpenWorkflow();
await openworkflow.registerConnector('./connectors/weather.yaml');
// Or from object
await openworkflow.registerConnector(connectorConfig);
Go:
import "github.com/Open-Workflow/sdk-go"
s := openworkflow.New()
err := s.RegisterConnector("./connectors/weather.yaml")
2. Action Handler Registration
For SDK function handlers, provide a decorator/annotation pattern:
Python:
from openworkflow import action
@action("weather.get_current_weather")
def get_weather(location: str) -> dict:
"""Handler implementation"""
return {"temperature": 72, "conditions": "sunny"}
# Or register manually
openworkflow.register_handler("weather.get_current_weather", get_weather)
JavaScript:
import { action } from '@openworkflow/sdk';
@action('weather.get_current_weather')
async function getWeather({ location }) {
return { temperature: 72, conditions: 'sunny' };
}
// Or register manually
openworkflow.registerHandler('weather.get_current_weather', getWeather);
Go:
func GetWeather(input map[string]interface{}) (map[string]interface{}, error) {
location := input["location"].(string)
return map[string]interface{}{
"temperature": 72,
"conditions": "sunny",
}, nil
}
s.RegisterHandler("weather.get_current_weather", GetWeather)
3. Workflow Execution
Execute workflows locally:
Python:
from openworkflow import Workflows
# Load workflows
workflows = Workflows.from_file("./workflows.yaml")
# Execute specific workflow
result = workflows.execute("Daily Weather Report")
# Execute with custom inputs
result = workflows.execute("Daily Weather Report", inputs={"location": "New York"})
# Execute all workflows
results = workflows.execute_all()
# Async execution
result = await workflows.execute_async("Daily Weather Report")
JavaScript:
import { Workflows } from '@openworkflow/sdk';
const workflows = await Workflows.fromFile('./workflows.yaml');
// Execute specific workflow
const result = await workflows.execute('Daily Weather Report');
// With inputs
const result = await workflows.execute('Daily Weather Report', { location: 'New York' });
// Execute all
const results = await workflows.executeAll();
4. Cloud Integration
Register with OpenWorkflow Cloud for managed execution:
Python:
openworkflow = OpenWorkflow(api_key="sk_...")
# Auto-register all loaded plugins
openworkflow.sync_to_cloud()
# Or register individual connector
openworkflow.register_connector("weather.yaml", cloud=True)
# Execute in cloud
workflow = Workflow.from_file("workflow.yaml")
result = workflow.execute(mode="cloud")
JavaScript:
const openworkflow = new OpenWorkflow({ apiKey: 'sk_...' });
await openworkflow.syncToCloud();
// Execute in cloud
const workflow = await Workflow.fromFile('workflow.yaml');
const result = await workflow.execute({ mode: 'cloud' });
SDK Configuration
Initialization Options
openworkflow = OpenWorkflow(
api_key="sk_...", # Optional: OpenWorkflow Cloud API key
router_url="http://...", # Optional: Custom MCP Router URL
execution_mode="local", # "local", "cloud", or "hybrid"
timeout=300, # Default timeout for actions
retry_config={ # Default retry behavior
"max_attempts": 3,
"backoff": "exponential"
},
environment={ # Environment variables for plugins
"API_KEY": "...",
},
secrets_manager=None, # Optional: custom secrets provider
observability={ # Optional: monitoring config
"enabled": True,
"endpoint": "http://...",
"sample_rate": 1.0
}
)
Execution Modes
Local Execution
Actions execute in the same process:
# All actions run locally
openworkflow = OpenWorkflow(execution_mode="local")
result = workflow.execute() # Runs in-process
Requirements:
- All action handlers must be registered or accessible via HTTP
- No network calls to OpenWorkflow infrastructure
Cloud Execution
Actions execute in OpenWorkflow Cloud:
openworkflow = OpenWorkflow(api_key="sk_...", execution_mode="cloud")
result = workflow.execute() # Runs in OpenWorkflow Cloud
Requirements:
- Valid API key
- Plugin must be published to registry or uploaded
Hybrid Execution
Mix local and cloud execution:
openworkflow = OpenWorkflow(execution_mode="hybrid")
# Per-workflow override
result = workflow.execute(mode="cloud")
# Per-action override in workflow definition
# actions:
# - name: local_action
# connector: connector:community/my-connector@1.0.0
# execution: local
# - name: cloud_action
# connector: connector:external/connector@1.0.0
# execution: cloud
Handler Contract
Action handlers must follow this signature:
Input
Handlers receive a dictionary/object with action input parameters:
def handler(input: dict) -> dict:
location = input.get("location")
# ...
Or use typed parameters (Python/TypeScript):
from pydantic import BaseModel
class WeatherInput(BaseModel):
location: str
@action("weather.get_current_weather")
def handler(input: WeatherInput) -> dict:
# input is validated automatically
return {"temperature": 72}
Output
Handlers must return:
- Success: Dictionary matching the action's output schema
- Error: Raise an exception
def handler(input: dict) -> dict:
if not input.get("location"):
raise ValueError("Location is required")
return {"temperature": 72, "conditions": "sunny"}
Error Handling
SDKs automatically catch exceptions and format errors using the standard error taxonomy.
Error Taxonomy
All SDK implementations must use this standardized error code taxonomy:
Error Codes
VALIDATION — Input validation failure
{
"status": "error",
"code": "VALIDATION",
"message": "Input validation failed: location is required",
"details": {
"field": "location",
"constraint": "required"
}
}
UNAVAILABLE — Backend or connector not available
{
"status": "error",
"code": "UNAVAILABLE",
"message": "Backend 'langchain' is not installed",
"details": {
"backend": "langchain",
"suggestion": "Install: pip install openworkflow-backend-langchain"
}
}
BACKEND_ERROR — Backend execution failure
{
"status": "error",
"code": "BACKEND_ERROR",
"message": "LangChain agent execution failed",
"details": {
"backend": "langchain",
"original_error": "OutputParserException: Could not parse LLM output"
}
}
TIMEOUT — Execution exceeded deadline
{
"status": "error",
"code": "TIMEOUT",
"message": "Step execution exceeded timeout of 30s",
"details": {
"timeout_ms": 30000,
"elapsed_ms": 31200
}
}
CANCELED — Execution canceled by user
{
"status": "error",
"code": "CANCELED",
"message": "Execution canceled by user",
"details": {
"canceled_at": "2025-10-11T12:00:00Z"
}
}
INTERNAL_ERROR — SDK or runtime internal error
{
"status": "error",
"code": "INTERNAL_ERROR",
"message": "Unexpected runtime error",
"details": {
"error_type": "NullPointerException",
"stack_trace": "..."
}
}
Secrets Management
SDKs support pluggable secrets providers:
Python:
from openworkflow.secrets import AWSSecretsManager, EnvSecretsProvider
# Use AWS Secrets Manager
openworkflow = OpenWorkflow(
secrets_manager=AWSSecretsManager(region="us-east-1")
)
# Use environment variables (default)
openworkflow = OpenWorkflow(
secrets_manager=EnvSecretsProvider()
)
# Custom provider
class CustomSecrets:
def get(self, key: str) -> str:
# Fetch from your secrets backend
return "secret_value"
openworkflow = OpenWorkflow(secrets_manager=CustomSecrets())
In handlers, access secrets via context:
@action("github.create_issue")
def create_issue(input: dict, context: dict) -> dict:
token = context["secrets"]["GITHUB_TOKEN"]
# Use token
Observability
SDKs emit telemetry for monitoring:
Logging
import logging
openworkflow = OpenWorkflow(
log_level=logging.INFO,
log_format="json" # or "text"
)
# SDK logs action execution, errors, timing
Metrics
SDKs expose metrics for:
- Action execution count
- Action duration
- Error rate
- Queue depth (for async execution)
Python (Prometheus):
from openworkflow.observability import PrometheusExporter
exporter = PrometheusExporter(port=9090)
openworkflow = OpenWorkflow(observability_exporter=exporter)
# Metrics available at http://localhost:9090/metrics
Tracing
OpenTelemetry integration:
from opentelemetry import trace
from openworkflow.observability import OpenTelemetryExporter
tracer = trace.get_tracer(__name__)
openworkflow = OpenWorkflow(
observability_exporter=OpenTelemetryExporter(
endpoint="http://jaeger:4318"
)
)
# Automatic span creation for each action
Workflow Context
Workflows have access to execution context:
@action("my_plugin.my_action")
def handler(input: dict, context: dict) -> dict:
# Context includes:
# - workflow_id: unique workflow execution ID
# - step_name: current step name
# - inputs: workflow-level inputs
# - secrets: secret values
# - environment: environment variables
# - previous_steps: outputs from previous steps
prev_result = context["previous_steps"]["fetch_data"]["output"]
return {"processed": prev_result}
HTTP Server Integration
For HTTP-based handlers, SDKs can create API endpoints:
Python (FastAPI):
from fastapi import FastAPI
from openworkflow.integrations.fastapi import SmartifyRouter
app = FastAPI()
openworkflow = OpenWorkflow()
# Auto-create /execute endpoints for all plugins
app.include_router(SmartifyRouter(smartify))
# Now available:
# POST /weather/execute
# POST /github/execute
JavaScript (Express):
import express from 'express';
import { createSmartifyRouter } from '@openworkflow/sdk';
const app = express();
const openworkflow = new OpenWorkflow();
app.use('/plugins', createSmartifyRouter(smartify));
// Available:
// POST /plugins/weather/execute
// POST /plugins/github/execute
Testing
SDKs provide testing utilities:
from openworkflow.testing import MockSmartify, mock_action
# Mock OpenWorkflow instance
smartify = MockSmartify()
# Mock action responses
openworkflow.mock_action("weather.get_current_weather", {
"temperature": 72,
"conditions": "sunny"
})
# Test workflows
workflow = Workflow.from_file("workflow.yaml")
result = workflow.execute(smartify=smartify)
# Assert results
assert result["steps"]["fetch_weather"]["output"]["temperature"] == 72
SDK Requirements Checklist
All language implementations must support:
- Plugin registration from YAML/JSON files
- Plugin registration from in-memory objects
- Action handler registration (decorator pattern)
- Local workflow execution
- Cloud workflow execution (with API key)
- HTTP handler invocation
- SDK function handler invocation
- Command handler invocation (shell scripts)
- Environment variable interpolation
- Secrets management (pluggable)
- Structured logging
- Error handling and formatting
- Input/output schema validation
- Retry logic with backoff
- Timeout handling
- Basic observability (metrics/tracing)
- Testing utilities
Reference Implementations
- Python: smartify-sdk-python
- JavaScript: @openworkflow/sdk
- Go: openworkflow/sdk-go
Next Steps
- Connector Schema - Define your connectors
- Workflow Schema - Compose actions into workflows
- Execution Modes - Deep dive on execution patterns