OpenBox LangGraph SDK — Span & OTel Flow
Interactive visualization of governance event flow
LangGraph
Handler
SpanProcessor
SpanCollector
OTel / HTTP hooks
hook_governance
OpenBox Core
Regular Tool
Subagent (task)
LLM Call
Architecture
Phase A: on_tool_start
on_tool_start("search_web")
_map_event()
→ ToolStarted
activate_span_collector()
set_activity_context()
register_trace
(trace_id→activity)
evaluate_event()
→ send ToolStarted
Core: ActivityStarted → verdict
mark_activity_started()
+ internal span "started"
Phase B: Tool executes httpx call
search_web() → httpx.get("https://wikipedia.org")
Path 1: OTel Request Hook
_httpx_async_request_hook
store span + governance
"started"
evaluate_async()
→ _build_payload()
SpanProcessor.get_activity_context_by_trace()
Path 2: Patched AsyncClient.send
_patched_async_send
execute + capture body +
"completed"
evaluate_async()
→ completed
with request_body + response_body
Core: hook_trigger=true → policy eval
Phase C: on_tool_end
on_tool_end("search_web")
drain_spans()
clear_activity_context()
internal span "completed"
+ send ToolCompleted
Core: ActivityCompleted → close row
Subagent: on_tool_start("task", subagent_type="researcher")
on_tool_start("task")
is_subagent?
YES
SKIP all span setup
activate_span_collector — SKIP
set_activity_context — SKIP
register_trace — SKIP
mark_activity_started — SKIP
internal span hooks — SKIP
Event-level governance: ToolStarted
__openbox: {subagent_name: "researcher", tool_type: "a2a"}
Core: Rego policy checks subagent_name
Subagent executes internally (invisible)
subagent.invoke() → new LangGraph graph → new asyncio.Tasks
Internal HTTP calls get new trace_ids → SpanProcessor has no context
→ spans silently dropped (correct behavior)
on_tool_end("task")
Event-level only: send ToolCompleted
Core: ActivityCompleted → close row
LLM Call: on_chat_model_start / end
on_chat_model_start("ChatOpenAI")
_GuardrailsCallbackHandler.on_chat_model_start()
Fires BEFORE LLM call — extracts user prompt
Send
LLMStarted
(activity_type="
llm_call
")
Core: Run guardrails (PII, content filter, toxicity)
→ return redacted_input if PII detected
Mutate messages in-place
(PII redaction)
LLM calls api.openai.com / api.anthropic.com
httpx hook fires but URL is in _LLM_PROVIDER_PREFIXES
_should_ignore_url() → IGNORED
on_chat_model_end
Core: LLMCompleted (tokens, model, completion)
LangGraph Event Stream
langgraph_handler.py
_map_event()
_process_event()
_pre_screen_input()
_GuardrailsCallback
subagent bypass
verdict enforcement
SpanCollector
per-tool instance
ContextVar-based lookup
OpenBox Core
/api/v1/governance/evaluate
Rego policy + guardrails + HITL
SpanProcessor
singleton (OTel interface)
trace_id → activity map
hook_governance.py
evaluate_sync() / evaluate_async()
hook_trigger=true
OTel Instrumentation Layer
http_governance_hooks
requests | httpx | urllib3 | urllib
db_governance_hooks
psycopg2 | asyncpg | mongo | redis | SA
file_governance_hooks
builtins.open() patch
otel_setup.py — orchestrates all instrumentors
tracing.py
— @traced decorator for internal function spans