Skip to content

Multi-turn agent history, compact trace storage, leaner judge evidence, clearer timeline - #79

Merged
SushantGautam merged 14 commits into
mainfrom
feat/agent-traces-compact-history
Nov 4, 2026
Merged

SushantGautam merged 14 commits into
mainfrom
feat/agent-traces-compact-history

Conversation

@SushantGautam

Copy link
Copy Markdown
Collaborator

Found while running real multi-turn traced audits against agent 1 (Support Refund Assistant) on a live model.

Agents keep the conversation across turns

Traced agent runs go through Open WebUI's saved-chat path, which builds the model input from the chat Open WebUI stores. Every turn created a new chat holding only the latest user message, so from turn 2 the agent answered without the conversation. This was confirmed in Open WebUI's database and in the model-call spans.

Each later turn now continues the scenario's chat (chat_id, plus the previous reply as parent_id), keyed by model, W3C trace id and the turns so far. Untraced runs were never affected: they send the full history themselves.

Native knowledge search in Open WebUI

acme_search_knowledge always failed inside Open WebUI ("Docs directory not found"), and the agent then fell back to Open WebUI's own knowledge search. It is removed from the tool pushed to Open WebUI, and the seeded agent prompt asks for the attached knowledge base. The keyword search stays as Studio's local stand-in for untraced runs (infra/fixtures/local_knowledge.py).

Trace content stored once per trace (audits/agentic/compact.py)

GenAI spans repeat the system prompt, the history and earlier tool results on every model call. Studio now stores trace evidence in the same reference format as the Open WebUI fork's compact mode, whichever target sent the spans:

  • a repeated message becomes {role, parts: [{type: reference, span_id, attribute, index}]};
  • a repeated value becomes {type: reference, …};
  • knowledge chunks are kept once, as retrieval documents.

References are expanded before rules and judges read the spans. On 1-turn runs this stores about a third less, and much less on multi-turn runs.

Judges see each piece once, from the trace

  • Main judge: reads selected spans through the SimpleAudit engine, which caps each span at 2,000 characters. It now gets a judge view:

    • only gen_ai.* attributes and the error fields (error.type, exception.*);
    • no workflow wrappers;
    • repeats shown as "(same as span …)" notes, and Open WebUI's RAG template dropped from user messages;
    • messages already in the conversation pointed to it;
    • decisions, results and documents placed before the input history.

    On a 5-turn run the judge's input tokens fell 28%, even though the agent now sees full history, and the spans cut by the length limit fell from 10 to 1.

  • Semantic agentic judge:

    • whole answers (the 800-character cap cut off conclusions);
    • every turn as the trace records it, with no Studio transcript;
    • no duplicated final answer or RAG template;
    • retrieval source names instead of whole document JSON.

Trajectory timeline

  • Repeats are shown as "↩ same as LLM-1 · …".
  • Retrieval steps show their query and retrieved documents, and JSON blocks are indented.
  • Failed steps get an error badge and an "Error" block ("ValueError: No order found …").
  • Model calls are tagged from GenAI output only: amber tool call or green reply. Every turn's reply is highlighted, not only the last model call of the trace.
  • 👤 marks the call that received the user's turn.
  • Distinct icons: 🧠 tool call, 💬 reply, 🔧 tool run.
  • The "agent → tool" rows now read GenAI tool_call parts. They used to read OpenAI's tool_calls, so they never showed on GenAI traces.

Demo scenarios work in multi-turn runs

  • D09 depended on SIMPLEAUDIT_DEMO_FAIL_FIRST, which nothing set and which failed only once per machine. It also capped tracking calls at 1–2 over the whole conversation. It now tests reporting tracking status only as observed, and the fail-first hook is removed.
  • D02 drops its "at most 1 error step" cap.
  • No count rule with an upper bound remains in the pack.

Open WebUI v0.12.4.9-subpath

Checks

  • uv run pytest -m "not embedded_hatchet": 1626 passed, 3 skipped
  • uv run ruff check .: clean
  • makemigrations --check: no changes
  • Live traced audits on Open WebUI 0.12.4.9 (1 and 5 turns), 10 of 10 scenarios each. Run 38 (5 turns): every scenario pass.

…ive knowledge search

Traced agent runs go through Open WebUI's saved-chat path, which builds the
model input from the chat it stores. Every turn created a new chat holding
only the latest user message, so from turn 2 the agent answered without the
conversation. Each later turn now continues the scenario's chat (chat_id and
the previous reply as parent_id), keyed by model, W3C trace id and the turns
so far; an unknown conversation starts a new chat as before, with a warning.

The Open WebUI order tool no longer carries acme_search_knowledge: inside Open
WebUI it always failed (its policy files are not on the Open WebUI host) and
the agent fell back to Open WebUI's own knowledge search. The keyword search
stays as Studio's local stand-in for untraced runs (fixtures/local_knowledge),
and the seeded agent prompt asks for the attached knowledge base.
GenAI spans repeat the system prompt, the history and earlier tool results on
every model call. Studio now stores trace evidence compacted in the reference
format of Open WebUI's compact mode (a repeated message becomes {role, parts:
[{type: reference, span_id, attribute, index}]}, a repeated value {type:
reference, ...}), whichever target sent the spans, and expands references
before rules and the judge read them (audits/agentic/compact.py).

The trajectory timeline shows each repeat as a '↩ same as LLM-1 · …' line,
including results stored before compaction. It also shows a retrieval
step's query and retrieved documents, and indents JSON content blocks.
v0.12.4.6: GenAI-only spans. v0.12.4.7: schema-shaped spans. v0.12.4.8:
agentic knowledge search traced as a retrieval span with retrieval documents.
v0.12.4.9: compact GenAI content by default (each piece once per trace).
…he trace

The evidence packet cut the agent's final answer at 800 characters, so a long
answer reached the judge without its conclusion and task_completion failed as
'truncated'. Answers now keep up to 6,000 characters. Multi-turn scenarios
answer the task across turns, but the judge compared only the last reply with
the first question; the packet now lists every turn as the trace records it
(each turn's last model call: the user message it received and the reply it
produced), still trace-only evidence.
…ents JSON

gen_ai.retrieval.documents is JSON text; the packet treated the string itself
as a source name, so retrieval_source_ids carried every retrieved chunk again.
The final response was sent twice (as final_response and as the last
assistant turn), and every user turn carried Open WebUI's fixed RAG template
and <context> source list, whose sources the retrieval steps already show.
The packet now keeps the last turn as the final response and gives the user's
own message (flagged rag_template_removed). Result 253's packet goes from
13.8 KB to 8.8 KB with the same verdict.
…ted failures

D09 depended on SIMPLEAUDIT_DEMO_FAIL_FIRST making the tracking tool fail once,
which no environment set (and whose /tmp marker made it fail once per machine
for good), and it capped tracking calls at 1-2 over the whole conversation, so
multi-turn runs failed it for ordinary follow-up calls. D09 now tests reporting
tracking status only as observed: it fetches ACME-1001's events, never another
order's, with no errors, and the semantic judge checks grounding and result
handling. The fail-first hook is removed from the demo tool. D02 drops its
'at most 1 error step' cap: each follow-up lookup of the unknown order rightly
errors again. No count rule with an upper bound remains in the pack.
…ed steps show their error

The main judge reads the selected spans through the SimpleAudit engine, which
dumps each span's attributes and cuts it at 2,000 characters, so a model call's
repeated input history pushed its output out and SDK metadata and workflow
wrappers padded the prompt. Studio now hands the judge a judge view
(audits.agentic.compact.judge_view): only gen_ai.* attributes and the error
fields, no workflow wrappers, content repeated from an earlier span as a short
'(same as span ...)' note, knowledge chunks once as retrieval documents, Open
WebUI's RAG template dropped from user messages, messages already in the
conversation pointed to it, and decisions, results and documents ahead of the
input history. On a 5-turn run the judge's input tokens fell 28% while the
agent now sees full history.

The trajectory timeline shows a failed step's exception ('ValueError: No
order found ...'), which tool calls that raise have instead of a result.
Each model-call row gets one badge from gen_ai.output.messages and
gen_ai.response.finish_reasons only: 'tool call' when the output asks for a
tool, 'reply' when it answers in text. Every turn's reply is highlighted,
not only the last model call of the whole trace. Tool-decision rows read
GenAI tool_call parts (they read OpenAI tool_calls, so 'agent -> tool' never
showed on GenAI traces).
A model call whose gen_ai.input.messages ends with a user message gets a 👤
marker: it is the call that took the user's new message (a compacted,
referenced message keeps its role).
Copilot AI balanced review requested due to automatic review settings November 4, 2026 20:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

…er lookup

acme_get_tracking_events returned any order's tracking, so after
acme_get_order refused ACME-1002 (CUST-002) an agent could still disclose its
timestamps and hub locations through the tracking tool. Both now share one
ownership check and refuse with the same response.
lifecycle had its own port probe: a plain bind, which refuses ports whose
connections linger in TIME_WAIT for up to a minute after 'dev down', so an
immediate restart failed with 'Ports unavailable'. It now uses ports'
probe, which binds with SO_REUSEADDR (not on Windows, where the option can take
over a live port) and first checks that nothing accepts on 127.0.0.1: with
SO_REUSEADDR a wildcard bind succeeds beside a loopback-only listener on macOS,
which the old ports probe missed.
@SushantGautam
SushantGautam merged commit 349b5ec into main Nov 4, 2026
2 of 3 checks passed
@SushantGautam
SushantGautam deleted the feat/agent-traces-compact-history branch November 4, 2026 21:04
SushantGautam added a commit that referenced this pull request Nov 5, 2026
test_concurrent_runs_keep_their_own_auditor_class called mock.patch from two
threads at once on the same attributes (decision_target_for,
decision_auditor_class, snapshot_api_key). mock.patch is not thread-safe: one
thread could restore the other's mock, leaving it installed for every later test
on that xdist worker. That is the CI flake seen on #79 and #81 (snapshot_api_key
returning '', decision_target_for returning object(), the auditor class being
AuditorA). The test now patches once in the main thread, with side effects that
pick each thread's class from its own target.
SushantGautam added a commit that referenced this pull request Nov 5, 2026
… cold start (#81)

* chore: pin Open WebUI v0.12.4.10-subpath

GenAI spans on every content, tool and model path (fork #11): retrieval spans
with documents for every content-reading built-in tool (knowledge files and
grep, chat files, notes, chats, channels, memories, web search and fetch) and
for memory recall, execute_tool spans in default function calling, and chat
spans for pipe models. Fixes checks that counted no retrieved documents when an
agent read a file with view_knowledge_file instead of searching.

* fix: traced agent runs survive Open WebUI's cold start

Right after Open WebUI starts, its model registry can still miss the agent when
Studio's once-a-minute refresh ran before Open WebUI loaded its connections, so
concurrent scenarios failed with 'Model not found'. A saved-chat request that
gets that answer now reloads the registry immediately and retries once.

* test: stop the concurrent-auditor test leaking mocks into later tests

test_concurrent_runs_keep_their_own_auditor_class called mock.patch from two
threads at once on the same attributes (decision_target_for,
decision_auditor_class, snapshot_api_key). mock.patch is not thread-safe: one
thread could restore the other's mock, leaving it installed for every later test
on that xdist worker. That is the CI flake seen on #79 and #81 (snapshot_api_key
returning '', decision_target_for returning object(), the auditor class being
AuditorA). The test now patches once in the main thread, with side effects that
pick each thread's class from its own target.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants