Observability: OpenTelemetry GenAI spans for turns + tool calls (closes #41) - #47
Merged
Conversation
lezama
marked this pull request as ready for review
May 18, 2026 10:39
lezama
force-pushed
the
ship/issue-41-otel-tracing
branch
from
May 18, 2026 10:41
89b8234 to
b11cd24
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #41
Summary
OpenclaWP_Tracerbuffers per-request spans in memory and batch-POSTs them as an OTLP/HTTP JSONExportTraceServiceRequestpayload viawp_remote_post()onshutdown. No PHP OTel SDK dependency — payload is hand-rolled per the OTLP spec.openclawp_chat_turn_completedemits the root chat span per turn;agents_api_loop_event(tool_call+tool_result) emits child tool spans parented to the active turn. One trace per PHP request, so a turn and all its tool calls show up as one trace tree in the backend.OPENCLAWP_OTEL_ENDPOINT(env),openclawp_otel_endpoint(WP option), or theopenclawp_otel_endpointfilter resolves to a non-empty URL. Optional auth header viaOPENCLAWP_OTEL_AUTH_HEADER/ option / filter.openclawp.session.id,openclawp.agent.slug,openclawp.user.id,openclawp.channel) is stamped onto every span by the runner before the loop runs.Attribute conventions (verified by tests)
GenAI semconv attributes are emitted verbatim:
gen_ai.system— provider id (e.g.anthropic,ollama)gen_ai.operation.name—chatfor root spans,execute_toolfor tool childrengen_ai.request.model— pinned model idgen_ai.usage.input_tokens/gen_ai.usage.output_tokens— intgen_ai.response.finish_reasons— string array (["stop"]/["error"])gen_ai.tool.name— on child spansSite-specific identifiers are namespaced under
openclawp.*.Overhead measurement (5ms budget)
bin/bench-tracer.phpdrives one synthetic turn + one tool call through the tracer 1000 times:~2µs per turn — three orders of magnitude under the 5ms budget. A PHPUnit test (
test_overhead_under_5ms_per_turn) enforces the budget on every CI run.Test plan
vendor/bin/phpunit --testsuite unit— 41 tests, 101 assertions, 10 new inTracerTestphp tests/smoke.php— exits 0 (no regressions in the WP-light path)npm run lint— cleanwp_remote_postbackend and asserts OTLP envelope shape + every GenAI semconv attribute is present with the right typephp bin/bench-tracer.phpDeferred (follow-ups, not blocking #41 close)
The issue lists a few "nice to have" items beyond the core tracer surface — moved to separate issues:
register()gatewp_ai_client_promptwe don't own yet