OpenBox SDK for Temporal Workflows
OpenBox SDK provides governance and observability for Temporal workflows by capturing workflow/activity lifecycle events, HTTP telemetry, database queries, and file operations, then sending them to OpenBox Core for policy evaluation.
Key Features:
- 7 event types (WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted, Handoff)
- Multi-agent sessions — propagate a shared
multi_agent_session_idacross workflow + activity events - 5-tier verdict system (ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT)
- Hook-level governance — per-operation evaluation (HTTP requests, file I/O, database queries, function tracing) with started/completed stages
- HTTP/Database/File I/O instrumentation via OpenTelemetry
- Guardrails: Input/output validation and redaction
- Human-in-the-loop approval with expiration handling
- Zero-code setup via
create_openbox_worker()factory
Installation
pip install openbox-temporal-sdk-python
Requirements:
- Python 3.11+
- Temporal SDK 1.23+ (1.8+ for factory-only usage)
- OpenTelemetry API/SDK 1.38.0+
Plugin Integration (Recommended)
Use OpenBoxPlugin for drop-in integration with Temporal Workers:
import os
from temporalio.worker import Worker
from openbox.plugin import OpenBoxPlugin
worker = Worker(
client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
plugins=[
OpenBoxPlugin(
openbox_url=os.getenv("OPENBOX_URL"),
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
)
],
)
await worker.run()
The plugin automatically configures governance interceptors, OTel instrumentation,
sandbox passthrough, W3C trace propagation through Temporal headers (via
temporalio.contrib.opentelemetry.TracingInterceptor), and the
send_governance_event activity.
Credentials never leave the plugin. openbox_api_key is captured on the
governance activity instance itself — it does not flow through activity
inputs, so it is never written to workflow history. To opt out of trace
propagation (e.g., if you already wire OpenTelemetryPlugin), pass
enable_trace_propagation=False.
Composing with Other Plugins
from temporalio.contrib.opentelemetry import OpenTelemetryPlugin
worker = Worker(
client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
plugins=[
OpenTelemetryPlugin(),
OpenBoxPlugin(openbox_url=..., openbox_api_key=...),
],
)
Requires
temporalio >= 1.23.0. For older versions, usecreate_openbox_worker()below.
Quick Start (Factory)
Use the create_openbox_worker() factory for simple integration:
import os
from openbox import create_openbox_worker
worker = create_openbox_worker(
client=client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
# OpenBox config
openbox_url=os.getenv("OPENBOX_URL"),
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
)
await worker.run()
The factory automatically:
- Validates the API key
- Creates span processor
- Sets up OpenTelemetry instrumentation
- Creates governance interceptors (incl. W3C trace propagation)
- Builds the
GovernanceActivitiesinstance with credentials captured onselfand registers itssend_governance_eventmethod — the API key is never passed through activity inputs / workflow history - Returns fully configured Worker
Configuration
Environment Variables
OPENBOX_URL=http://localhost:8086
OPENBOX_API_KEY=obx_test_key_1
OPENBOX_GOVERNANCE_TIMEOUT=30.0
OPENBOX_GOVERNANCE_POLICY=fail_open # or fail_closed
Factory Function Parameters
worker = create_openbox_worker(
client=client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
# OpenBox config
openbox_url="http://localhost:8086",
openbox_api_key="obx_test_key_1",
governance_timeout=30.0,
governance_policy="fail_open",
# Event filtering
send_start_event=True,
send_activity_start_event=True,
skip_workflow_types={"InternalWorkflow"},
skip_activity_types={"send_governance_event"},
skip_signals={"heartbeat"},
# Database instrumentation
instrument_databases=True,
# File I/O instrumentation
instrument_file_io=False, # disabled by default
# Header-based W3C trace propagation (client → workflow → activities).
# Default True. Set False if you already wire OpenTelemetryPlugin or a
# custom propagator.
enable_trace_propagation=True,
# Standard Worker options (all supported)
activity_executor=my_executor,
max_concurrent_activities=10,
)
Governance Verdicts
OpenBox Core returns a verdict indicating what action the SDK should take.
| Verdict | Behavior |
|---|---|
ALLOW |
Continue execution normally |
CONSTRAIN |
Log constraints, continue |
REQUIRE_APPROVAL |
Pause, poll for human approval |
BLOCK |
Raise error, stop activity; if carrying a retry_plan, restart the workflow with replacement input via Continue-As-New (see "Retryable BLOCK" below) |
HALT |
Raise error, terminate workflow |
Retryable BLOCK: When a BLOCK verdict includes a retry_plan with new_input, the SDK restarts the workflow using the corrected input instead of failing permanently. The restart chain is bounded by max_retryable_block_restarts (default: 3). See Retryable BLOCK Restart Configuration for semantics and critical idempotency requirements.
v1.0 Backward Compatibility:
"continue"→ALLOW"stop"→HALT"require-approval"→REQUIRE_APPROVAL
Event Types
| Event | Trigger | Captured Fields |
|---|---|---|
| WorkflowStarted | Workflow begins | workflow_id, run_id, workflow_type, task_queue |
| WorkflowCompleted | Workflow succeeds | workflow_id, run_id, workflow_type |
| WorkflowFailed | Workflow fails | workflow_id, run_id, workflow_type, error |
| SignalReceived | Signal received | workflow_id, signal_name, signal_args |
| ActivityStarted | Activity begins | activity_id, activity_type, activity_input |
| ActivityCompleted | Activity ends | activity_id, activity_type, activity_input, activity_output, spans, status, duration |
| Handoff | Agent hands off to another agent | from_agent_did, multi_agent_session_id |
When a workflow runs as part of a multi-agent session (see below), every event
above also carries multi_agent_session_id. The field is omitted when no
session id is supplied.
Multi-Agent Sessions
Group the work of several agents under one shared multi_agent_session_id. The
SDK only propagates the id you supply — it never invents one, and it owns no
routing, session minting, or agent registry (those stay in your application).
Supply the id via the Temporal workflow memo at start time:
await client.start_workflow(
MyWorkflow.run,
arg,
id="order-123",
task_queue="my-queue",
memo={"openbox_multi_agent_session_id": session_id},
)
The SDK then tags every governance event (workflow, activity, and hook events) with that id, propagating it from the workflow to its activities automatically.
Emit an explicit handoff from inside workflow code:
from openbox import emit_handoff
await emit_handoff(
multi_agent_session_id=session_id,
from_agent_did="did:aip:...", # the sending agent
)
The receiving agent is derived server-side from the signed identity and is never
sent. emit_handoff validates its arguments locally before any network call.
Guardrails (Input/Output Redaction)
OpenBox Core can validate and redact sensitive data before/after activity execution:
# Request
{
"verdict": "allow",
"guardrails_result": {
"input_type": "activity_input",
"redacted_input": {"prompt": "[REDACTED]", "user_id": "123"},
"validation_passed": true,
"reasons": []
}
}
# If validation fails:
{
"validation_passed": false,
"reasons": [
{"type": "pii", "field": "email", "reason": "Contains PII"}
]
}
Error Handling
Configure error policy via the governance_policy parameter ("fail_open" / "fail_closed"):
| Policy | Behavior |
|---|---|
fail_open (default) |
If governance API fails, allow workflow to continue |
fail_closed |
If governance API fails, terminate workflow |
Supported Instrumentation
HTTP Libraries
httpx(sync + async) - full body capturerequests- full body captureurllib3- full body captureurllib- request body only
Databases
Fully supported — any dbapi-compatible library using OTel's CursorTracer.traced_execution():
psycopg2,pymysql,mysql-connector-python, and other dbapi-compliant drivers
Custom hooks (best-effort) — these libraries use non-dbapi instrumentation paths; governance hooks may not work correctly in all scenarios:
asyncpg— wrapt wrapper on Connection methods (governance runs outside OTel span context)pymongo— CommandListener monitoring + wrapt Collection wrappers (dedup via thread-local flag; some internal commands likeendSessionsonly producecompletedstage)redis— native OTelrequest_hook/response_hooksqlalchemy—before/after_cursor_executeevent listeners
SQLAlchemy Note: Query-level governance works on pre-existing engines automatically — even engines created before create_openbox_worker() runs (e.g., at module import time) — because the base runtime governs SQLAlchemy via a global Engine event listener. No engine handle needs to be passed in. The db_libraries and sqlalchemy_engine parameters are still accepted for backward compatibility but are no longer required and have no effect: the base runtime installs every available database instrumentor best-effort.
worker = create_openbox_worker(
...,
instrument_databases=True, # default; installs all available DB instrumentors
)
File I/O
open(),read(),write(),readline(),readlines()- Skips system paths (
/dev/,/proc/,/sys/,__pycache__)
Hook-Level Governance
Every HTTP request, file operation, and database query made during an activity is evaluated by OpenBox Core in real-time at two stages:
HTTP Requests
| Stage | Trigger | Data Available |
|---|---|---|
started |
Before request is sent | Method, URL, request headers, request body |
completed |
After response received | All of above + response headers, response body, status code |
File Operations
Per-operation governance evaluates every read()/write()/readline()/readlines()/writelines() call, not just open/close:
| Operation | Stage | Trigger | Data Available |
|---|---|---|---|
open |
started |
Before file is opened | File path, open mode |
read |
started |
Before read executes | File path, mode |
read |
completed |
After read returns | data (content read), bytes_read |
readline |
started |
Before readline executes | File path, mode |
readline |
completed |
After readline returns | data (line read), bytes_read |
readlines |
started |
Before readlines executes | File path, mode |
readlines |
completed |
After readlines returns | data (lines read), bytes_read, lines_count |
write |
started |
Before write executes | File path, mode |
write |
completed |
After write returns | data (content written), bytes_written |
writelines |
started |
Before writelines executes | File path, mode |
writelines |
completed |
After writelines returns | data (lines written), bytes_written, lines_count |
close |
completed |
After file is closed | bytes_read, bytes_written, operations list |
How it works (HTTP):
- OTel httpx instrumentation fires a request hook → SDK sends
startedgovernance evaluation with request data - If verdict is BLOCK/HALT → request is aborted before it leaves the process
- After response arrives → SDK sends
completedgovernance evaluation with full request+response data - If verdict is BLOCK/HALT →
GovernanceBlockedErroris raised, activity fails withGovernanceStop
How it works (File I/O):
- Activity calls
open()→ SDK sendsstartedgovernance evaluation with file path and mode - If verdict is BLOCK/HALT → file is never opened,
GovernanceBlockedErroris raised - Each
read()/write()/readline()/readlines()/writelines()call sendsstarted(before) andcompleted(after) governance evaluations — enabling content-based policy enforcement - After file is closed → SDK sends
completedgovernance with lifecycle summary (total bytes, operations list) - File governance requires
instrument_file_io=True(disabled by default)
A simple open-read-close produces 4 governance evaluations: open(started) → read(started) → read(completed) → close(completed).
Database Queries
Every database operation is evaluated at started (pre-query, can block) and completed (post-query, reports outcome):
| Field | Started | Completed |
|---|---|---|
type |
"db_query" |
"db_query" |
stage |
"started" |
"completed" |
db_system |
postgresql, mysql, mongodb, redis, sqlite | same |
db_name |
Database name | same |
db_operation |
SQL verb or command (SELECT, INSERT, GET, etc.) | same |
db_statement |
Query string or command | same |
server_address |
Host | same |
server_port |
Port | same |
duration_ms |
— | Query duration in ms |
error |
— | Error message or None |
How it works:
- Activity executes a DB query (via any supported library)
- SDK governance hook intercepts before the query → sends
startedevaluation - If verdict is BLOCK/HALT → query is aborted,
GovernanceBlockedErrorraised - Query executes normally → SDK sends
completedevaluation with duration and error status - DB governance is automatic when
instrument_databases=True(default)
Per-library strategy:
| Library | Hook Method | Can Block? | Reliability |
|---|---|---|---|
| psycopg2, pymysql, mysql-connector-python | CursorTracer.traced_execution patch |
Yes | Fully supported |
| asyncpg | wrapt wrapper on Connection methods |
Yes | Best-effort |
| pymongo | CommandListener + wrapt Collection wrappers |
Yes (wrapt only) | Best-effort |
| redis | Native OTel request_hook/response_hook |
Yes | Best-effort |
| sqlalchemy | before/after_cursor_execute events |
Yes | Best-effort |
Note: Libraries marked "Fully supported" use OTel's
CursorTracer, which guarantees governance hooks run inside the OTel span context. Best-effort libraries use custom hooks that may produce inconsistencies (e.g., missing stages for internal commands). Some C extension types (e.g.,psycopg2.extensions.cursor) cannot be patched withwrapt— in those cases governance hooks are silently skipped, but OTel span capture still works normally.
Function Tracing
Functions decorated with @traced are automatically governed when a worker (or plugin) has installed the base runtime. @traced wraps the base SDK's governed() decorator:
| Stage | Trigger | Data Available |
|---|---|---|
started |
Before function executes | Function name, module, arguments (if capture_args=True) |
completed |
After function returns/raises | All of above + result (if capture_result=True) or error info |
How it works (Function Tracing):
@traceddelegates to the base SDK'sgoverned()decorator- → the base runtime sends a
startedFUNCTION_CALL evaluation with function name and module - If verdict is BLOCK/HALT → the call is blocked and the function never executes
- Function executes normally
- → the base runtime sends a
completedFUNCTION_CALL evaluation with result or error info - If verdict is BLOCK/HALT → the block is surfaced after execution
- When no base runtime is installed → transparent passthrough, zero governance calls
Architecture
See System Architecture for detailed component design.
High-Level Flow:
Workflow / Activity lifecycle → Temporal SDK interceptors → OpenBox Core API
↓
Returns Verdict
↓
(ALLOW, BLOCK, HALT, REQUIRE_APPROVAL)
Hook-Level (per HTTP request / DB query / file op / @traced fn):
Operation → base-SDK instrumentation → OpenBox Core API (started) → Allow/Block
→ Operation runs → OpenBox Core API (completed) → Allow/Block
The Temporal worker/plugin builds and owns an openbox_core runtime that installs
all hook instrumentation. Hook payload building, evaluation, and enforcement are
owned entirely by the base SDK; the Temporal SDK maps the resulting verdicts onto
Temporal-native effects.
Responsibility split:
Base SDK (openbox_core):
- HTTP / DB / file / function hook instrumentation
- Hook payload shape, evaluation, and enforcement
Temporal SDK (this package):
workflow_interceptor.py/activity_interceptor.py— workflow/activity/signal lifecycle governance events (via workflow-safe activities)governance_state.py—TemporalGovernanceState: signal-verdict bridging, HITL pending-approval markers, completed-hook stop/halt propagationcore_adapter.py— builds the base runtime (create_core_runtime) and maps base verdicts to Temporal effects (TemporalFrameworkAdapter)
Advanced Usage
The supported integration is a single call. Both create_openbox_worker(...) and
OpenBoxPlugin(...) build and own the base openbox_core runtime, install all
hook instrumentation, register the governance interceptors, and wire the
send_governance_event activity for you — there is no manual OpenTelemetry or
span-processor setup to perform.
Worker factory:
import os
from openbox import create_openbox_worker
worker = create_openbox_worker(
client=client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
openbox_url=os.getenv("OPENBOX_URL"),
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
governance_policy="fail_closed",
governance_timeout=30.0,
instrument_databases=True,
instrument_file_io=True,
)
await worker.run()
Plugin (Temporal >= 1.23.0) — attach to a plain Worker:
from temporalio.worker import Worker
from openbox.plugin import OpenBoxPlugin
worker = Worker(
client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
plugins=[OpenBoxPlugin(
openbox_url=os.getenv("OPENBOX_URL"),
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
governance_policy="fail_closed",
)],
)
Both entry points accept the same governance, HITL, signing, and instrumentation options (see Configuration).
Documentation
- Project Overview & PDR - Requirements, features, constraints
- System Architecture - Component design, data flows, security
- Codebase Summary - Code structure and component details
- Code Standards - Coding conventions and best practices
- Project Roadmap - Future enhancements and timeline
Testing
The SDK includes comprehensive test coverage under tests/:
pytest tests/
Coverage spans the worker factory and plugin, workflow/activity/signal
interceptors, HITL approval flow, request signing, public-API compatibility, and
parity with the base SDK's hook governance (test_temporal_hook_parity.py,
test_core_conformance_suite.py). Hook instrumentation internals (HTTP/DB/file
payload shape) are verified in the base SDK's own conformance suite.
License
MIT License - See LICENSE file for details
Support
- Issues: GitHub Issues
- Documentation: See
./docs/
Version: 1.2.0 | Last Updated: 2026-04-05
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 openbox_temporal_sdk_python-1.3.0.tar.gz.
File metadata
- Download URL: openbox_temporal_sdk_python-1.3.0.tar.gz
- Upload date:
- Size: 510.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ef37fa59a9f21298b532b2f574394e5b929204966c64f73cc8d56ad0fac1333
|
|
| MD5 |
748363f2502b722a03f2d561d21a3b1b
|
|
| BLAKE2b-256 |
f7fcb4c92ec0b5e589a80dbbcb4f183a10dccb39f1ad45c1c3c92caadb34b1fd
|
File details
Details for the file openbox_temporal_sdk_python-1.3.0-py3-none-any.whl.
File metadata
- Download URL: openbox_temporal_sdk_python-1.3.0-py3-none-any.whl
- Upload date:
- Size: 66.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67dd565250bea7f3fa3ab514002deb85abf94bc4aeca300312fdcf8a576ac578
|
|
| MD5 |
985ab144e5e40db618b3c998570fb972
|
|
| BLAKE2b-256 |
bc6fb21782c80d6047c262e5076f6cab25a60473f62f2e630822d3703c51c3e9
|