Skip to content

feat(agents): opt-in capture of raw LLM request/response bodies - #3843

Merged
hvent90 merged 6 commits into
mainfrom
feat/llm-wire-trace
Sep 1, 2026
Merged

feat(agents): opt-in capture of raw LLM request/response bodies#3843
hvent90 merged 6 commits into
mainfrom
feat/llm-wire-trace

Conversation

@hvent90

@hvent90 hvent90 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Contribution path

  • Small, safe change that does not need a tracking issue

Problem

We need to save agent trajectories of our MCP client but it at present has no way of saving that information.

Solution

An opt-in process that instruments the httpx client for OpenAI requests. The default behavior does not affect any existing hot paths. The first consumer will be the eval framework: #3774

Auth headers are not saved.

Langchain does not have a provider-agnostic hook for capturing raw requests/responses, so it would require more tinkering for other providers. Because we just use OpenAI, I kept it simple. Maybe we'll be off Langchain by the time we need universal coverage :)

How to Test

uv run python - <<'EOF'
from pathlib import Path
from dimos.agents.llm_trace import tracing_http_client

tracing_http_client(Path("/tmp/llm-trace")).post(
    "https://api.openai.com/v1/chat/completions", json={"ping": 1})
print(sorted(p.name for p in Path("/tmp/llm-trace").iterdir()))
EOF # -> Prints `['000-request.json', '000-response.json']`

AI assistance

Fable 5 wrote 99.9% of this. I reviewed every line.

Checklist

  • I have read and approved the CLA.

tracing_http_client wraps the OpenAI wire so every provider call lands
whole under <run log dir>/llm as <seq>-request/-response.json pairs;
McpClient wires it into init_chat_model and exposes trace_dir() over
RPC so harnesses can link agent steps to the exact payloads.
Tracing now happens only when McpClientConfig.trace_dir is set
explicitly; a plain dimos run no longer taps the wire or writes under
<run log dir>/llm. Harnesses that want capture pass trace_dir.
isinstance(model, ChatOpenAI) on the init_chat_model result replaces
_attempt_infer_model_provider; a langchain bump can no longer break
McpClient at import time. Traced set unchanged.
write_normalized and latest_pair have no callers here; they land with
the eval framework that consumes them. This module is now just the tap.
@hvent90 hvent90 changed the title Feat/llm wire trace feat(agents): opt-in capture of raw LLM request/response bodies Aug 31, 2026
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.00000% with 6 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/agents/llm_trace.py 91.83% 4 Missing ⚠️
dimos/agents/mcp/mcp_client.py 84.61% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #3843      +/-   ##
==========================================
+ Coverage   77.58%   77.60%   +0.01%     
==========================================
  Files        1315     1317       +2     
  Lines      124697   124794      +97     
  Branches    10867    10868       +1     
==========================================
+ Hits        96748    96841      +93     
- Misses      24816    24820       +4     
  Partials     3133     3133              
Flag Coverage Δ
OS-ubuntu-24.04-arm 74.25% <94.00%> (+0.01%) ⬆️
OS-ubuntu-latest 74.79% <94.00%> (+0.01%) ⬆️
Py-3.10 74.79% <94.00%> (+0.01%) ⬆️
Py-3.11 74.79% <94.00%> (+0.01%) ⬆️
Py-3.12 74.79% <94.00%> (+0.01%) ⬆️
Py-3.13 74.79% <94.00%> (+0.01%) ⬆️
Py-3.14 74.80% <94.00%> (+0.01%) ⬆️
Py-3.14t 74.79% <94.00%> (+0.01%) ⬆️
SelfHosted-Large 30.22% <36.00%> (+<0.01%) ⬆️
SelfHosted-Linux 34.74% <39.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/agents/test_llm_trace.py 100.00% <100.00%> (ø)
dimos/agents/mcp/mcp_client.py 83.55% <84.61%> (-0.17%) ⬇️
dimos/agents/llm_trace.py 91.83% <91.83%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds opt-in raw OpenAI request/response tracing and exposes the trace directory through MCP client configuration.

A concurrent shared trace directory can lose or misassociate records: independently created tracing clients can select the same sequence number and overwrite a request/response pair. The non-OpenAI initialization path was also exercised; although it temporarily constructs an unused tracing client, that client is immediately collected before opening connections, so it does not produce the reported connection-pool leak.

The change is not safe to merge until trace sequence allocation is made atomic for all clients and processes writing to the same directory.

Confidence Score: 4/5

Not safe to merge while concurrent writers can overwrite trace pairs in a shared trace directory.

Focused executable checks reproduced the shared-directory overwrite using the real tracing hooks and disproved the reported retained-client leak on the non-OpenAI path.

Files Needing Attention: dimos/agents/llm_trace.py needs directory-scoped, cross-process-safe trace sequence reservation. dimos/agents/mcp/mcp_client.py was exercised and needs no change for the reported client-lifetime concern.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex ran both the sequential baseline harness and the concurrent collision harness and reviewed their outputs.
  • T-Rex executed the focused non-OpenAI tracing-client lifecycle harness with and without a trace directory, and verified that no requests were made and no resources remained allocated after initialization.
  • T-Rex verified the exact tracing commands and observed results, including per-client locking preventing cross-client synchronization and the difference in files produced by the baseline and the concurrent runs.
  • T-Rex produced a formal finding proof for a posted P1 finding.
  • T-Rex concluded that the non-OpenAI tracing path does not pose a security issue and has negligible overhead, based on the tracing harness runs.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Shared trace directories allow independently-created clients to overwrite trace pairs

    • Bug
      • When two tracing_http_client instances submit requests concurrently to the same trace directory, both can count zero request files and choose sequence 0. The focused runtime harness made both production count operations complete before either write; both HTTP requests returned 200, but the directory retained only 000-request.json and 000-response.json, losing client-B's trace record.
    • Cause
      • _next_seq() is a non-atomic len(glob(...)) allocation at dimos/agents/llm_trace.py:43-44, while the threading.Lock() at line 73 is captured separately by every client instance. It cannot coordinate allocations across independently-created clients sharing trace_dir.
    • Fix
      • Use allocation synchronized at the trace-directory scope, including across processes if shared directories are supported. For example, hold a shared per-directory lock plus allocate with an atomic create (open(..., 'x')) and retry on FileExistsError; use an OS-backed file lock or another atomic allocator for multi-process callers. Preserve request/response pairing by reserving the sequence before writing the request.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "refactor(agents): drop the pair-reading ..." | Re-trigger Greptile

Comment thread dimos/agents/llm_trace.py
@hvent90
hvent90 force-pushed the feat/llm-wire-trace branch from bed7ea3 to 22cf720 Compare August 31, 2026 22:53
spomichter
spomichter previously approved these changes Aug 31, 2026
@spomichter
spomichter enabled auto-merge August 31, 2026 22:55
Codecov failed PR 3843 on patch coverage: llm_trace.py landed at 38.77%
with the hook bodies untested. Exercise tracing_http_client over an
httpx.MockTransport: record pair written with auth header dropped,
non-JSON bodies kept as text, sequence numbers increasing.
@spomichter
spomichter added this pull request to the merge queue Aug 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 31, 2026
@hvent90
hvent90 added this pull request to the merge queue Aug 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 1, 2026
@hvent90
hvent90 added this pull request to the merge queue Sep 1, 2026
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Sep 1, 2026
Merged via the queue into main with commit 25a3121 Sep 1, 2026
32 checks passed
@hvent90
hvent90 deleted the feat/llm-wire-trace branch September 1, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants