Repository navigation
feat(ag2): traceAI-ag2 for AG2 1.x via native TelemetryMiddleware (TH-8236) - #210
Merged
Merged
Conversation
The Node OTLP exporter streams with Transfer-Encoding: chunked and sends no Content-Length. The Receiver read Content-Length only, so every Node export came back 400 with zero spans; two TH-8103 children had to put a de-chunking relay in front of it. Receiver now accepts both framings. It also records one entry per accepted export (path, lower-cased headers, flattened resource attributes) via requests(), so a contract test can assert X-Api-Key/X-Secret-Key and project_name through the shared harness instead of a private recorder. Verified: 6 harness tests pass, and the TanStack example's real Node exporter delivered 4 chunked exports (4 spans, collector path, both auth headers, project_name/project_type) with no relay. Refs: TH-8339, TH-8103
New package python/frameworks/ag2 (traceAI-ag2, import traceai_ag2) for AG2 1.x (PyPI ag2, import ag2). Native-first per D-8236: - setup(*agents, tracer_provider=None, capture_content=False, ...) registers the Future AGI exporter (register(project_type=OBSERVE) when no provider is passed) and attaches AG2's own TelemetryMiddleware to each agent through the public Agent.add_middleware, overriding the upstream capture_content=True default. Idempotent per agent. Agent is not wrapped or patched. - create_telemetry_middleware() for agents built later or per-call middleware. - AG2SpanProcessor, prepended ahead of the exporter, on the opentelemetry.instrumentation.ag2 scope only: sets gen_ai.span.kind from gen_ai.operation.name (chat/execute_tool/invoke_agent), copies cache_creation_input_tokens, cache_read_input_tokens and thinking_tokens to the dotted GenAI semconv keys (originals kept), and applies TraceConfig as a second content gate. - ag2>=1.0.0 (first stable 1.x with TelemetryMiddleware), no dependency on autogen or autogen-agentchat; fi-instrumentation-otel>=1.0.0. Tests: processor unit tests, setup tests against real ag2 with its scripted TestConfig model, import isolation (no autogen), wire-level resource/header test, and shared-harness Receiver contract tests over real OTLP/HTTP. Refs: TH-8236
record_usage model_call repeats the chat span's input/output tokens (ag2 1.1.2 telemetry.py 437-440 vs 501-504). fi-collector promotes gen_ai.usage.* into the token columns on any span (adapter.go inputTokenKeys/outputTokenKeys/totalTokenKeys) and Observe sums total_tokens over every span of a trace (span_reader v2, tasks/session.py), so the trace total doubled. On model_call and aggregation usage spans the processor now moves the promoted keys to ag2.usage.*. Values are kept. subtask and compaction usage spans keep their tokens: no chat span repeats them. Tests first: the contract test now asserts the summed promoted input tokens equal one model call; 2 new processor tests. 52 passed on ag2 1.1.2, 50 passed + 2 skipped on 1.0.3. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
Review of #210 (reproduced with real ag2 1.1.2) found two token-total errors against AG2's own UsageReport: - aggregation: ag2 aggregate.py 97-99 / 193-195 call the model client on a throwaway Context(MemoryStream()) that never reaches on_llm_call, so the record_usage aggregation span is the only copy of that spend. Demoting it dropped it from the trace (11 promoted vs 111 actual input tokens). 'aggregation' is removed from the demoted kinds; compaction stays kept. - subtask: the rollup (run_task.py 63-93, label = sub-agent name) repeats the sub-agent's chat spans when that sub-agent is itself instrumented (91 vs 51). AG2SpanProcessor now records agent names from ended invoke_agent spans per trace in a bounded LRU map (1024 traces) and moves a subtask rollup's promoted tokens to ag2.usage.* only when its ag2.usage.label names an agent seen in the same trace. An uninstrumented worker's rollup keeps its tokens (the only copy). Comment, module docstring, README usage table and CHANGELOG describe the moves per usage kind. Tests first, red on ec9fc07 (7 failed): new tests/test_usage_totals.py runs real AG2 with the scripted TestConfig model and asserts summed promoted input and output tokens equal UsageReport for aggregation, instrumented and uninstrumented subagent_tool workers, a namesake in another trace, and one run covering main loop + compaction + aggregation + subtask; synthetic processor tests for aggregation/compaction kept, subtask demoted/kept and the bounded map. Mutants "demote every subtask" and "agent names not scoped per trace" each turn guard tests red. 63 passed on ag2 1.1.2, 61 passed + 2 skipped on 1.0.3. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
Published fi-instrumentation-otel 1.0.0 crashes in register() with opentelemetry-exporter-otlp-proto-http 1.45.0 (otel.py:289 AttributeError: 'HTTPSpanExporter' object has no attribute '_headers'). Evidence, this branch's suite with PYTHONPATH=python/frameworks/ag2: python/tests (no in-repo fi_instrumentation) and ag2 1.1.2: - --with fi-instrumentation-otel==1.0.0: 5 failed, 58 passed (3x the _headers AttributeError, 1x register() got an unexpected keyword argument 'timeout') - --with fi-instrumentation-otel==1.1.0: 63 passed Floor raised in pyproject.toml with the reason; README and CHANGELOG note it. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
R4: a second setup(..., config=TraceConfig(hide_inputs=True, hide_outputs=True)) on a provider that already had an AG2SpanProcessor was silently ignored, so content was exported despite the hide request (reviewer probe: gen_ai.input/output.messages still on the chat span). install_span_processor now applies an explicit config to the installed processor (AG2SpanProcessor.update_config; most recent call wins) and logs a warning when it changes. config=None keeps the installed config, so a later setup() never re-exposes content. R5: on_start did nothing, so using_session / using_user / using_metadata / using_attributes were ignored on AG2 spans. on_start now copies fi_instrumentation get_attributes_from_context() onto AG2-scope spans without overriding keys AG2 already set (later AG2 set_attribute calls still win), except session.id: the context session is re-applied in on_end so it beats a static span_attributes value. Pending sessions are kept in a bounded map keyed by (trace_id, span_id) and released on end. Foreign-scope spans are untouched. README (setup, sessions, conformance table), setup()/processor docstrings and CHANGELOG updated. Tests first, red on ec9fc07: test_later_setup_config_replaces_installed_ processor_config, test_using_session_sets_session_on_every_ag2_span, test_using_attributes_context_reaches_ag2_spans, test_context_session_wins_but_other_ag2_keys_are_not_overridden, test_pending_context_sessions_are_bounded_and_released_on_end (also red against a no-eviction mutant: 40 == 16). Guards that stay green: test_later_setup_without_config_keeps_installed_config, test_context_attributes_skip_foreign_scope_spans. 70 passed on ag2 1.1.2. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
R6: AG2 records message content as one JSON string per span
(gen_ai.input.messages / gen_ai.output.messages, telemetry.py 477/517 at
1.1.2), never as the flattened gen_ai.input.messages.{i}.message.content
keys that TraceConfig.mask's hide_input_text / hide_output_text /
hide_input_images rules match. With capture_content=True, those flags
left every prompt and completion in place, while the module docstring
and README claimed "the other TraceConfig.mask rules" applied.
The processor now drops whole attributes (privacy-safe; the text inside
the JSON cannot be redacted on its own):
- hide_inputs / hide_input_text: gen_ai.input.messages,
gen_ai.system_instructions, gen_ai.tool.call.arguments,
ag2.human_input.prompt
- hide_outputs / hide_output_text: gen_ai.output.messages,
gen_ai.tool.call.result, ag2.human_input.response
- hide_input_messages: gen_ai.input.messages, gen_ai.system_instructions
- hide_output_messages: gen_ai.output.messages
hide_input_images and hide_embedding_vectors have nothing to act on (AG2
serialises only text parts and records no embeddings); a real-AG2 test
pins that image bytes never reach a span. Module docstring, README
(privacy table of which flags apply) and CHANGELOG say so.
Tests first, red on fbaac8e (7 failed, 75 passed; final test files copied
into a detached fbaac8e worktree): test_trace_config_flags_on_ag2_json_
content[hide_input_text, hide_output_text, hide_inputs,
hide_input_messages, hide_input_text+hide_output_text],
test_text_hide_flags_remove_ag2_message_json and
test_hide_input_text_keeps_outputs (real AG2). Passing on fbaac8e by
design: the hide_outputs / hide_output_messages params,
test_flags_with_nothing_to_act_on_leave_ag2_content,
test_ag2_never_records_image_bytes. Green: 82 passed on ag2 1.1.2;
75 passed, 7 skipped on ag2 1.0.0.
approved: Nikhil 2026-10-03 blanket
Refs: TH-8236
R7: the provider from fi_instrumentation.register() keeps _default_processor=True, so the application's first provider.add_span_processor() call after setup() shut down and cleared the whole chain (fi_instrumentation/otel.py add_span_processor, lines 329-340), AG2SpanProcessor included. The new exporter then got un-normalized AG2 spans (no gen_ai.span.kind, no usage moves, no TraceConfig content gate). install_span_processor now wraps the provider instance's add_span_processor once (marker attribute): the AG2 processor is lifted out of the chain for the call, so fi's reset neither shuts it down nor drops it, and is put back first afterwards. provider.shutdown() still shuts it down. Installing on a provider whose active processor is a ConcurrentMultiSpanProcessor logs a warning, since parallel on_end calls do not guarantee normalization before export. Docstring, README (set-up note, troubleshooting) and CHANGELOG updated. Tests first, red on 4a9922e (2 failed, 2 passed): test_later_add_span_processor_on_fi_provider_keeps_ag2_processor_first (chain was just the new SimpleSpanProcessor) and test_install_warns_when_processors_run_concurrently. Passing on 4a9922e by design: test_later_add_span_processor_on_sdk_provider_keeps_ ag2_processor_first and test_provider_shutdown_still_shuts_the_processor_ down; mutant "do not put the processor back" turns both later_add tests red. Green: 86 passed on ag2 1.1.2; 79 passed, 7 skipped on ag2 1.0.0. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
…spans R7: AG2SpanProcessor.on_end replaced span._attributes with a plain dict. ReadableSpan.dropped_attributes (exported as dropped_attributes_count) reads BoundedAttributes.dropped and returns 0 for anything else, so every normalized AG2 span reported 0 dropped attributes, and the span's max_span_attributes / max_attribute_length limits and its ended-span immutability were lost. on_end now rebuilds a BoundedAttributes with the previous maxlen, max_value_len and immutability, and carries the previous dropped count over. Keys the processor adds count against the same limit: on a span already full, the oldest key is evicted and counted, as the SDK does (only reachable at the limit, 128 by default). Docstring, README note and CHANGELOG updated. Tests first, red on c1196a4 (2 failed): test_normalizing_keeps_the_sdk_dropped_attribute_count (assert 0 == 1 on span.dropped_attributes) and test_keys_the_processor_adds_count_against_span_limits (plain dict kept 3 keys over a limit of 2). Green: 88 passed on ag2 1.1.2. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
…g again setup() without tracer_provider called fi register() on every call, so setup(planner) then setup(worker) put each agent on its own provider with its own AG2SpanProcessor. The planner's processor never saw the worker's invoke_agent span, kept the subtask rollup's promoted tokens, and the trace counted the worker's spend twice (real AG2: 91/15 input/output tokens against a UsageReport of 51/11). setup() now remembers the provider it registered and reuses it on later calls without tracer_provider until that provider is shut down. A later config= still replaces the installed TraceConfig; a different project_name is ignored with a warning. README and CHANGELOG updated. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
…bute limits on_start writes the traceAI context keys (session.id, user.id, metadata, ...) before AG2 sets any attribute, so they are the oldest keys on every AG2 span. When the keys the processor adds (span kind, usage aliases) overflow max_span_attributes, BoundedAttributes evicts oldest-first, so session.id and user.id went first and the span dropped out of its session's totals. on_end now moves the context keys behind AG2's keys before re-bounding, so AG2's oldest keys are evicted instead. The eviction count is unchanged. Test: real AG2 chat span with using_attributes at a lowered limit. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
…tured setup() adds TelemetryMiddleware as the innermost middleware. Observed with real AG2 (1.1.2 and 1.0.0) and now pinned by tests: - a middleware registered before setup() that answers on_llm_call without calling call_next leaves no chat span (invoke_agent ends UNSET); - one that answers on_turn leaves no span at all; - AG2's halt (HaltEvent from AlertPolicy) with Agent(assembly=...) policies leaves no chat span, because the halt check is added before telemetry; - with per-call Plugin policies (ag2 1.1.2 only) the halted call gets a chat span with status UNSET and no attribute carrying the halt reason. No capture is added. README gains a Limits section; CHANGELOG notes it. approved: Nikhil 2026-10-03 blanket Refs: TH-8236
- Tested-version line names only what the suite ran: ag2 1.0.0, 1.0.3 and
1.1.2 on Python 3.11, and ag2 1.1.2 on Python 3.10, 3.12 and 3.13.
- hide_llm_invocation_parameters has nothing to act on: AG2 1.0.0 and 1.1.2
set no gen_ai.request.parameters. A drift guard now checks AG2's
telemetry source for that key; the processor docstring says the same.
- Limits: the subtask dedupe matches by agent name within a trace (M1);
create_telemetry_middleware() defaults agent_name=None ("unknown") and
tracer_provider=None (OTel global provider) (N2); concurrent first
add_span_processor calls can race with fi's unlocked reset (N4).
approved: Nikhil 2026-10-03 blanket
Refs: TH-8236
Future AGI fills a span's input and output only from input.value / output.value. AG2 sets neither: it records chat messages, tool arguments and results, and human input under their own keys. With capture_content=True, a real run on CLIProxy showed every LLM and tool span with empty input and output in Observe, though the content was on the span. The processor now copies that content to input.value / output.value (with a mime type) on chat, execute_tool and await_human_input spans. Content a TraceConfig flag hides is not copied, and existing values are kept. With capture off (the default) nothing changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
AG2's cache_read_input_tokens / cache_creation_input_tokens were aliased to the dotted semconv names gen_ai.usage.cache_read.input_tokens / cache_creation.input_tokens. Future AGI's attribute registry reads gen_ai.usage.cache_read_tokens / cache_write_tokens and never the dotted names, so cache counts never reached it. Alias to those keys instead (the originals stay). A record_usage span that repeats a chat span's usage moves them to ag2.usage.* with the token counts, so each call carries them once. Checked live: an AnthropicConfig agent on CLIProxy (AG2's default prompt caching) stored cache_read_tokens 9747 and cache_write_tokens 9749 on its chat spans in ClickHouse, and ag2.usage.* on the record_usage spans. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…erve Anthropic input tokens: AG2's Anthropic mapper reports prompt_tokens without cache reads and writes (its OpenAI mapper includes them), and AnthropicConfig caches by default, so a chat span showed 4 input tokens for a 9,750-token prompt. On anthropic spans whose tokens count, cache_read_input_tokens and cache_creation_input_tokens are now added to gen_ai.usage.input_tokens, and total_tokens is recomputed when present. The trace total can then exceed AG2's UsageReport by the cached tokens (README, CHANGELOG). Duplicate record_usage spans keep AG2's own numbers under ag2.usage.*. Chat reply: a single completion goes on output.value as its message object. Observe's chat view shows that as the assistant reply and shows nothing for a one-item list. Agent I/O: invoke_agent carries no content in AG2. With capture_content=True it now gets the first chat input and the last chat output of its turn, so the trace list shows the question and the answer. Hidden content is not copied, and the agent's own values win. Checked live on CLIProxy: Anthropic chat spans store 9751/94 and 9753/88 tokens; the capture run's trace list shows Input/Output, and the chat span shows the assistant reply bubble. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ck traces or raw error text (TH-8236) Content is captured by default and hidden with FI_HIDE_* / TraceConfig like every traceAI instrumentor; capture_content now defaults to True (AG2's own default). Tracebacks and absolute local paths are removed from content attributes, ERROR status descriptions carry the exception class only, exception events keep exception.type only. Verified on a real run against local Future AGI. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
Summary
New Python package
traceAI-ag2for AG2 1.x (TH-8236). It is native-first: it attaches AG2's ownTelemetryMiddlewarewithcapture_content=Falseand addsAG2SpanProcessorahead of the Future AGI exporter. It does not wrapAgent, and it does not depend onautogenorautogen-agentchat.invoke_agent,chatandexecute_toolmap to AGENT, LLM and TOOL.await_human_inputandrecord_usagespans get no kind rather than a guessed one.record_usage model_callrepeats the chat span's tokens (ag2 1.1.2telemetry.py437-440 vs 501-504).gen_ai.usage.*into the token columns on any span (fi-collector/pkg/adapter/adapter.go), and Observe sumstotal_tokensover every span of a trace (tracer/services/clickhouse/v2/span_reader.py,tracer/tasks/session.py).ag2.usage.*only where they would be counted twice. Onmodel_callspans the chat span already carries the same tokens. Asubtaskrollup is moved only when that worker is instrumented in the same trace (agent names tracked per trace, bounded).aggregationkeeps its tokens. A real-AG2 test checks that the trace total equals AG2'sUsageReport.hide_inputs/hide_outputs, tool arguments, tool results and human-input text are also dropped (TraceConfig.maskdoes not cover them).hide_input_text/hide_output_textdrop AG2's JSON message attributes; the README lists which flags apply.setup(config=...)replaces the installed config and logs a warning.on_startcopies traceAI context (using_sessionetc.) onto AG2 spans.AG2SpanProcessorstays first after a lateradd_span_processorcall: fi's call resets the chain, so the processor is lifted out and put back first. AConcurrentMultiSpanProcessorgets a warning. Normalized spans keepBoundedAttributesand the dropped-attribute count. Atmax_span_attributes(128 by default), an added key pushes out the oldest one and the drop is counted (README, CHANGELOG).Deviations from the accepted architecture (with evidence)
setup(*agents, ...)takes the agents. AG2 has no process-wide middleware registry: middleware attaches per agent (plugin.py:176 add_middleware) or per call.create_telemetry_middleware()covers agents built later.ag2>=1.0.0, not>=1.1.2.TelemetryMiddlewareis present from 1.0.0 (checked across every 1.x wheel).max_tool_result_charsis forwarded only when set (it exists from 1.0.4).span_attributes={"session.id": ...}, and that path is tested.fi-instrumentation-otel>=1.1.0. 0.1.x lacks the span-kind constants, and published 1.0.0 crashes in_headers(5 tests fail on it).Tests (run on d64de18, clean tree, by the coordinator)
No file changed outside
python/frameworks/ag2, and no secret-like lines were added.Review
Round 1, an independent fresh-context review of ec9fc07 (a subagent, not an installed card): CHANGES_REQUESTED. Fixed test first on top of ec9fc07. All final tests run against ec9fc07 give 23 failed / 65 passed; 21 of those are real assertion failures.
aggregationtokens wrongly moved offUsageReportsubtaskrollup counted twice when the worker is instrumentedfi-instrumentation-otel>=1.1.0setup()config ignoredon_startcopies it; contextsession.idwinsadd_span_processor(2 failed before);BoundedAttributeskept (2 failed before)Round 2: installed pr-reviewer, then pr-verifier, on 9c2c1bf. Verdicts will be added here.
Round 2 follow-ups (9c2c1bf..d64de18)
The installed review passed this PR at
9c2c1bf: pr-reviewer t_0d642613 APPROVE, pr-verifier t_62f6dbc6 VERIFIED. Its non-blocking findings were still fixed before the demo, test-first. Run against9c2c1bf, the new tests give 4 failures on ag2 1.1.2 and 3 on 1.0.0.setup()registered a second provider and counted subtask spend twicesetup()registers once and reuses that provider until it is shut down (2d7cd80); the failing case was(91, 15)vs(51, 11)tokenssession.idwas the first key evicted at the attribute limit2ffef0a)Plugin(1.1.2), and no span withAgent(assembly=...); both are tested (c0a2015)gen_ai.request.parametersis documented as never emitted by ag2 (d64de18)unknownagent name, concurrent first-call raceRound 3 (installed pr-verifier t_3f913b48 at
d64de18) returned VERIFIED, ready to merge.d64de18on a clean tree: ag2 1.0.3 on Python 3.11 gave 94 passed, 3 skipped; ag2 1.1.2 on Python 3.10 and 3.12 gave 97 passed each. The line now stands as written.Review is complete at
d64de18. One P3 follow-up remains, for after merge:setup()registered itself.setup(a, tracer_provider=P)followed bysetup(b)still registers a second provider. Agents set up before a provider shutdown keep that provider's middleware on a latersetup(). One README sentence would cover both.Video demo
Narrated terminal demo, 6:28, recorded at head
d64de18(the reviewed head), 1080p H.264/AAC, 9 chapters. sha25641a51914bff49cee8abc49ecdecc7305782dcb22f18b9ce08551e4e09086664b.The video, captions, transcript, chapter list, preview and media check are attached privately to Linear TH-8236 (Future AGI workspace access needed). Every run uses AG2 1.1.2 with a fake model, placeholder keys and the shared harness receiver: no vendor call and no live fi-collector.
d64de18: branch, 0 uncommitted files, 12 commits on3eaadc8/tracer/v1/traceswith both auth headers (values not shown); 5 spans in 1 trace with AGENT / LLM / TOOL kinds; promoted tokens 11/7 over the trace = one model call counted once; no content keys and no prompt, answer or tool text by defaultusing_attributes,session.id=demo-session-42anduser.id=demo-user-7on all 5 spans9c2c1bf, 2 providers and 2 exports, trace tokens 91/15 against an AG2 UsageReport of 51/11 (the worker's spend counted twice); at the head, one provider, 1 export, 51/112d7cd80, withmax_span_attributes=14the LLM span losessession.idanduser.id; at the head the 4 drops fall on AG2's own keys and both are kept_setup.py(2d7cd80) and the eviction order in_processor.py(2ffef0a)Under recording load VHS dropped frames, so each shot holds its final verified frame under the narration (no retiming). Checked by the coordinator: sha256, full decode, chapter markers, pauses under 3 s, frames per chapter against the driver output, no keys or header values in frames, captions or transcript.
Limits
TestConfig.network/client/handlers.py:255does it.setup()call is in the README.Stacked on #203. Not merged.
Linear: TH-8236. approved: Nikhil 2026-10-03 blanket
Verification sweep (2026-10-12)
dev, and the spans were checked in ClickHouse and in the Observe UI (hierarchy, span types, input/output, model and tokens, sessions, error status, and a secret scan of everything stored).gen_ai.usage.cache_read_tokens/cache_write_tokens; for Anthropic with prompt caching,input_tokensnow includes cached tokens, so the trace total can exceed AG2's own UsageReport.Re-verification (2026-10-16)
Verdict: not correct as-is, two fixes pushed (5b9a6b5). Everything else held.
Versions. ag2 1.1.2 (latest stable on PyPI; the PR's pin is the same, floor
>=1.0.0), fi-instrumentation-otel 1.1.0, opentelemetry 1.45.1, Python 3.12. Tests (offline suite, commit 5b9a6b5): ag2 1.1.2 py3.12 130 passed; ag2 1.0.3 py3.11 127 passed, 3 skipped; ag2 1.0.0 py3.11 121 passed, 9 skipped.Real E2E. Real AG2 agents (
OpenAIConfig(model="gpt-6-luna")through CLIProxy) with a tool (get_weather) and a failing tool (broken),setup(a, b, tracer_provider=...),using_session, exported to local Future AGI projectth8103-rick-ag2.fi_verifyPASS (llm, tool, agent present):Hierarchy, model, tokens, session id (same on all spans of a run) and ERROR status checked in ClickHouse and the Observe UI (screenshot checked by eye).
What was broken (found on the real run) and fixed.
capture_content=Falsewith its own opt-in, which differs from the traceAITraceConfigconvention (content on by default,FI_HIDE_*hides it). Nowcapture_content=True(AG2's own default), no second switch;FI_HIDE_*/TraceConfighide content. Tests updated/added (default captures,TraceConfigandFI_HIDE_*env hide it).str(exc)(here containing a path). Before the fix, scanning all stored columns of the failure trace found/Users/...,Traceback,site-packages, the username and the raw exception text. NowAG2SpanProcessorcuts tracebacks ([stack trace removed]), replaces absolute local paths ([path]), sets the ERROR description to the exception class name and keeps onlyexception.typeon exception events, in both modes. Tests added (test_error_text_paths_and_stack_traces_are_not_exported, traceback in tool message,scrub_text). README and CHANGELOG updated.Privacy proof (unique marker, scan of ALL ClickHouse
spanscolumns viatoString(tuple(*)), spans of the new runs only).th8103-rick-ag2, markerAG2MK1792196281, 12 spans): prompt marker found in 7 spans, tool result in 2 (content on, as intended);/Users/someone,Traceback,secret.txt,site-packages,/Users/, local username: 0 spans. The tool's raw exception message appears only inside the model's own reply text (model output content), never in status, events or attributes.FI_HIDE_INPUTS=true FI_HIDE_OUTPUTS=true(projectth8103-rick-ag2-hidden, markerAG2HID1792196298, 12 spans):-PROMPT,-TOOLRESULT,-ERR,-SYS,/Users/someone,Traceback,secret.txt: 0 spans. The marker itself appears only in the session id I set (<marker>-session), 12 spans.Docs. Page added in future-agi/docs#991 (draft, base main, no navigation.ts; suggested nav: Integrations / traceAI, title "AG2"). Build and audit-links pass.
Unchanged and still open. The P3 F2 follow-up above; AG2 network hub not run; no hosted AG2 service involved.
Sweep fix (2026-10-18)
Question. The 2026-10-18 sweep's E2E read showed the
broken_botrun (tool raises) withchat/execute_toolspans as roots and noinvoke_agentroot, whileweather_bothad its root. Checked whether the agent span is not created, not ended, or loses context on the failure path.Verdict: not an instrumentation bug. No code change; head stays 5b9a6b5. The
invoke_agent broken_botspan is created, ended and exported as the parent of every span in its turn. The sweep read ClickHouse before the last export batch, which carries that root, was stored.Evidence (ag2 1.1.2, fi-instrumentation-otel 1.1.0, Python 3.12, CLIProxy
gpt-6-luna, local Future AGI, the same e2e script, unchanged, with only the OTLP exporter'sexportwrapped to log span name / id / parent / status):weather_botspans, root included. Batch 2 at 01:23:37.583, the 5-secondBatchSpanProcessortick in the middle of thebroken_botturn:chat,record_usage,execute_tool broken(ERROR), each with parentf279005280564b74. Batch 3 at 01:23:41.769, onforce_flush(): the secondchat,record_usageandinvoke_agent broken_bot(f279005280564b74, no parent). All 12 spans were exported before the process exited (01:23:41.774).spans, polled every second from exit): 01:23:42.06 had 9 spans, 1invoke_agent, 3 orphans; 01:23:43.16 and later had 12 spans, 2invoke_agent, 0 orphans.fi_verify --min-spans 2started right after exit returned at the 9-span state and printed the sweep's exact tree (chat,record_usage,execute_tool brokenas roots).fi_verify --min-spans 12then showedinvoke_agent broken_bot> chat, record_usage, execute_tool broken (ERROR:RuntimeError), chat, record_usage. Projectth8103-sweepfix-th-8236, tracedc2638bc-c5ec-4246-8385-218f021b2522.th8103-sweep-th-8236), queried again now: 12 spans, and trace64cc9068-b04b-472a-ba77-07418bf7fce5has itsinvoke_agent broken_botroot. ClickHousecreated_atshows that root, the second chat and itsrecord_usagearrived together at 23:55:04.27 UTC, about 5 s after the other 9 broken-bot-side rows (23:54:59.18). The sweep's 9-span read happened between the two.TestConfig, in-memory exporter, both agents viaask): a tolerated tool error gives one trace per agent, oneinvoke_agentroot, no orphans, the root ending last, root status UNSET (stored as OK). A turn that raises (raise_tool_errors=True) gives the same tree withinvoke_agentERROR.ag2/middleware/builtin/telemetry.pyon_turn):invoke_agentis set to ERROR only when the turn raises. In the E2E the tool error is sent back to the model, which answers, soinvoke_agent broken_botis OK andexecute_tool brokenis ERROR. Existing tests already cover the ERROR root when the turn raises (test_model_call_exception_sets_error_and_closes_spans,test_provider_exception_sets_error_and_closes_spans).pytest python/frameworks/ag2/tests --noconfteston 5b9a6b5: 130 passed. Privacy spot check on the new run: no/Users/someonein input, output or attributes of any span; the run's prompt marker is stored (content on by default).Documented on the docs page (future-agi/docs#991, commit 7e5fbd0): the
invoke_agentstatus rule for tool failures, and a troubleshooting entry forchat/execute_toolspans seen without their root while the last batch is still arriving.🤖 Generated with Claude Code