feat(11-B): hero_tracing.py SDK — span emission for Python flows #16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/11-phase-b-hero-tracing-sdk"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase B of hero_logic#11. Pure-Python (stdlib only) SDK that flow authors import. The executor (Phase C) embeds it via
include_str!and stages it on startup at~/.hero/var/flows/sdk/hero_tracing.py, then setsPYTHONPATHso flows canfrom hero_tracing import flow, instrument.Public API
@flow(name, inputs, description)__hero_flow__metadata; doesn't execute anything itself.flow.step(name, **tags)flow.span(name, **tags)step.flow.current_span_NullSpanif none open.flow.Failed(reason)instrument(client)__getattr__-based proxy — every public method opens anrpc:{Class}.{method}span.Wire protocol — JSONL over UDS at
HERO_FLOW_SPAN_SOCKFour event types:
span_start,span_tag,span_log,span_end. Times in ms-since-epoch. Documented in the module docstring as the source of truth for the Rust-side parser landing in Phase C.Standalone mode
When
HERO_FLOW_SPAN_SOCKis unset every helper is a silent no-op. Flow authors canpython my_flow.pyfor local development without the executor.Robustness
Test plan
python3 -m unittest discover -s crates/hero_logic/sdk/python/tests— 16/16 pass.flow.Failedvs arbitrary exception (clean reason vs traceback); tag/log events;HERO_FLOW_PARENT_SPANenv-var propagation (sub-flow case);instrument()including private-method passthrough + nested inheritance;_bootstrap_runhappy + failed paths; threaded concurrency (each thread gets its own root).Phase plan (#11 split)
hero_tracing.pySDKA and B are mergeable in any order. C combines them.
🤖 Generated with Claude Code
Phase B of hero_logic#11. Pure-Python (stdlib only) SDK that flow authors import to get @flow / flow.step / flow.current_span / instrument(). The executor (Phase C) embeds this file via include_str! and stages it on startup at ~/.hero/var/flows/sdk/hero_tracing.py. Wire protocol — JSONL over Unix domain socket pointed at by HERO_FLOW_SPAN_SOCK. Four event types: span_start, span_tag, span_log, span_end. All times in ms-since-epoch. Documented in the module docstring so a Rust-side parser can be written from the same source of truth. Public surface: - @flow(name=, inputs=, description=) — decorator. Stamps __hero_flow__ metadata onto the function so the executor can introspect inputs without running the flow. Calling the decorated function directly is a no-op of the original, so flows stay testable from a REPL. - flow.step(name, **tags) / flow.span(name, **tags) — context managers that open a child span. Uses contextvars so async tasks and threads inherit parent correctly. - flow.current_span — descriptor returning the open span or a no-op _NullSpan (so flow.current_span.tag(...) is unconditional). - flow.Failed(reason) — exception that marks the span failed with a clean reason instead of a traceback. Any other exception captures full traceback into the span's error field. - instrument(client) — __getattr__-based proxy that wraps every public method with an "rpc:{Class}.{method}" span. Service-agnostic; works against any generated client without per-client knowledge. Private (_-prefixed) methods pass through unwrapped. - _bootstrap_run(entry, input_data) — internal entry the executor's bootstrap stub will call to open the root span around the @flow function. Standalone-mode behavior: when HERO_FLOW_SPAN_SOCK is unset, every helper is a silent no-op. A flow author can `python my_flow.py` for local development without the executor running. Robustness: socket writes are thread-safe (per-writer lock). If the executor closes the socket mid-run (e.g. wall-clock timeout fired), the writer marks itself dead and silently drops subsequent events rather than raising broken-pipe inside the user's flow. Param/result serialization truncates at 4 KiB to keep the span tree readable. Tests: 16 unittest cases across 5 classes — standalone-mode no-ops, single + nested + failed steps, tag/log events, parent_span_id env-var propagation (sub-flow case), instrument() wrapping including private-method passthrough and parent inheritance, _bootstrap_run, and threaded concurrency. Run with: python3 -m unittest discover -s crates/hero_logic/sdk/python/tests Refs hero_logic#11 (Story 1: Foundation), hero_logic#10 (epic). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>