Proxytrace 1.9.0
Fixed
-
The documented image address works again. Every published reference to the container image —
the compose file shipped in the release artifact, the one-linedocker runin the README, the
installation, deployment and upgrade pages of the manual — pointed at
ghcr.io/proxytrace/proxytrace, a GitHub Container Registry owner that stopped existing when the
organisation was renamed toSyntaktikEU. Pulls against it fail withowner not found, so a new
operator following the quick start got a registry error instead of a running Proxytrace. All of
them now name the canonicalghcr.io/syntaktikeu/proxytrace, which is where the images have
actually been published. The Docker Hub address,proxytrace/proxytrace, is unchanged and was
never affected. -
A test run no longer hangs forever because one case failed. A case whose model call threw was
deliberately skipped so the rest of the run could continue — but the run counted results to decide
it was finished, and the skipped case never produced one. The run therefore sat at Running for
as long as the server stayed up, while the group it belonged to already read Completed, and a
restart did not clear it. Runs are now settled once every case has been attempted: Completed
when all of them produced a result, Failed when any were skipped — visibly incomplete instead
of eternally in progress. Runs already stranded this way are cleaned up on the next start. -
A broken judge no longer reads as a failing test case. When an LLM evaluator errored, the case
it was scoring counted as not passing, so a crashed judge was indistinguishable from an agent
that behaved badly — it dragged the run's pass rate down and could turn an optimization theory into
"could not test". A case is now decided by the evaluators that actually returned a verdict; errored
ones are left out, and a case is only unjudged when every evaluator on it errored. -
A long-winded LLM judge no longer throws away its own verdict. An evaluator that talked past
its output budget had its answer cut off mid-sentence, which made the whole response unreadable —
including the score it had already given. Judges are now asked to keep their reasoning brief, a
failed judge call is retried once, and an answer that was cut off is repaired and read rather than
discarded, so the verdict survives even when the explanation does not. -
Opening a trace from a link now scrolls the list to it. Following a trace link — from an
anomaly, a notification, or Tracey — opened the detail drawer but left the list showing the newest
traces, so the row you were sent to was never highlighted or brought into view. The older the
trace, the more likely it was. The list now loads until it reaches the linked trace and scrolls it
into the middle of the view, with the surrounding traces around it. -
A session's trace and token counters now go down when traces are deleted. They were only ever
incremented, so deleting a trace — by hand, or because it passed the retention window — left the
session header claiming more traces than its timeline could show, permanently. Both retention and
the delete action now give back exactly what the removed traces contributed. -
Sessions no longer accumulate forever. Session rows outlived their traces indefinitely, so a
client that mints a fresh session key per run grew the table without bound — including sessions
whose traces were long gone. The nightly trace cleanup now removes sessions whose last activity has
passed the same retention window, which by definition means every trace they grouped is already
gone. Sessions with recent traces are never touched. -
Edits to an agent no longer fail for minutes after a busy save. Under concurrent traffic — an
agent being updated while its traces were still arriving and the UI was open — the in-process entity
cache could refill with the pre-save version of a row and keep serving it for up to five minutes.
Every write attempted against that stale copy was rejected as a conflict, so ingestion retried and
saves failed for no visible reason. Cached entries are now dropped again once the save commits, so
the stale copy cannot outlive the write that replaced it. -
The proxy logs each upstream request once, not four times. Every call your agents made through
the proxy emitted four identical sets of HTTP client log lines, quadrupling the volume on the
busiest path in the system and making proxy logs hard to read during an incident. -
Proxytrace is documented as source-available, consistently. The Docker Hub overview still
declared the product Proprietary, contradicting the Elastic License 2.0 relicense in 1.5.0 — the
first licensing statement most evaluators read. It now states the ELv2 terms, the README carries a
matching License section, and every documented GitHub link (plus the release-manifest URL the
update check calls) points atSyntaktikEU/Proxytraceinstead of relying on GitHub's rename
redirect. -
The install quick starts now advertise the project-scoped proxy URL. The installation page,
the Docker Hub overview and the deployment artifact's README, compose file and.envtemplate all
told new users to point their agents athttp://localhost:5102/openai/v1— the legacy unscoped
form, which only works with a Proxytrace-issued key. Anyone following the documented one-line
migration, keeping their existing upstream provider key, got a 401 instead of a trace, because
the project is read from the URL path in that case. All five now point at the project-scoped
endpoint the setup wizard hands you,http://localhost:5102/{project-slug}/openai/v1, matching
what the manual's Proxy Setup page has always defined as canonical. -
The licensing manual no longer overstates the Free tier. It advertised 3 users while the
code allows 1 — and contradicted its own tier table further down the page — so an operator
could plan a three-person pilot and hit a blocked invite on the second seat. The page now says one
user, explains that this effectively disables user management until an upgrade, and lists the
scheduled test runs and custom anomaly detectors that were missing from the Enterprise column. -
Expanded multi-turn conversations no longer overlap the rows beneath them. Opening a
conversation's turns while new traces were streaming in left the expanded turns painted on top of
the following rows, with the text of both stacked on itself. The table measured each row by its
position in the list, so an arriving trace — which shifts every row below it down a slot — handed
an expanded group's height to whichever row inherited its old position. Rows are now measured by
identity, so they keep their own height however the list shifts around them. -
A newly captured trace no longer redraws the whole Traces table. While you watched the list at
the top, every arriving call blanked the table back to loading skeletons and rebuilt it — a flicker
on every request your agents made, and it threw away the rows you had already scrolled in. Arrivals
are now inserted in place, among the rows already on screen, and briefly highlighted so the new
one is easy to spot. Nothing else moves. A burst of calls is collected into one insert rather than
one reload each, and under a metric sort (slowest-first, say) the new trace lands where it actually
ranks instead of jumping to the top. A duplicated row that could appear after new traces arrived
and you scrolled on for the next batch is fixed with it.
Added
-
Ask Tracey a specific question from a trace. The trace detail action now opens a multiline
question box instead of immediately sending a generic analysis request. Ask what matters for the
call, such as why a refund was approved, and Tracey starts a fresh conversation with the trace ID
and your exact question. -
Tracey writes the failing test before she proposes a fix. Report a defect in a captured call —
"the agent in trace5b71…approved a refund even though the return window had expired" — and
Tracey now works it test-first. She reproduces it from the real conversation (and stops if the
trace doesn't show what you described), states the rule that was broken, then turns it into a
test case whose expected answer is what the agent should have said rather than what it did
say, attaching an LLM judge that can actually score that rule. She runs the suite and checks that
specific case: if it unexpectedly passes, the test doesn't capture your bug and she fixes the
test, not the agent; if the evaluator itself errored she says so, because a broken judge is not
evidence the agent was wrong. Only with a confirmed failure does she propose a change, and when
the background A/B test finishes she checks your case again against the candidate to show it move
from failing to passing. Anomaly detection can only flag calls that look unusual; this covers
the ones that look perfectly normal and are simply wrong. -
Tracey can read a whole trace. Asking Tracey about a captured call previously gave her only its
headline numbers (model, status, tokens, latency, cost) — enough to describe the call, but not to
read it. She can now pull the complete trace instead: every message of the request (system
prompt, user turns, assistant replies, tool calls and their results), the full response, the tool
schema the agent was offered, and the model parameters the call ran with. So "why did this call
fail?", "what was this agent actually told?" and "summarize this trace" are answered from the real
conversation rather than from metadata, and trace-driven work — diagnosing anomalies, grounding an
optimization theory, curating a suite from traces — is based on what was really said. She keeps
using the quick summary for "how big / how slow / how much" questions. -
Debugging sessions: group live traces across agents and conversations. Tag your calls with the
x-proxytrace-session-idheader and Proxytrace collects every trace sharing that key — spanning
multiple agents and conversations — into one session, the bigger picture around a single app run
or user session. Sessions are auto-created on the first trace with an unseen key, work on every
license tier, and need no setup. A dedicated session page (/sessions/:sessionId) shows one session's
traces as a live, chronological timeline: header counters (trace and token totals,
first-seen/last-activity) and the trace list update in real time as new calls arrive, with a Live
indicator while the session saw activity in the last five minutes. On the Traces page, a new
Session filter narrows the table (and its timeline) to a single session — pick from the project's
recent sessions — and every trace row and the trace detail panel carry a Session link to jump
straight to the whole session. For the API,GET /api/sessions?projectId=…lists a project's recent
sessions (most recently active first, with per-session trace and token counters) and
GET /api/sessions/{id}returns one; sessions are scoped to the projects you can access, exactly
like traces. -
Notification details view. Clicking a notification in the bell inbox now opens a detail drawer
instead of navigating away: the full, untruncated message, its kind, status, project and
timestamps, and a live summary of whatever the notification is about (test run, agent, proposal
or trace) with a link to it. If that item has since been deleted the drawer says so rather than
linking nowhere — a notification is often the only record an anomaly ever had. The drawer steps
through the inbox with prev/next, is deep-linkable on any page via?notification=<id>, and
notification emails now link straight to it (/notifications/<id>) instead of to the target's
list page. -
German language selection for the sample client. The sample chat client in the kiosk showcase now has an EN/DE toggle in the header. UI chrome, agent display names, and example shortcuts (including a stage-ready German version of the trick message) switch to German instantly; the agent system prompt, tool definitions, and
X-Proxytrace-Agentattribution header remain byte-identical English so ingestion attribution and the optimizer loop are unaffected. -
One-command live showcase stack. The kiosk now serves an OpenAI-compatible proxy in-process
when a live LLM endpoint is configured, so a sample client pointed at the demo can generate calls
that appear as traces in real time. Copykiosk.env.exampleto.env, fill in your credentials,
and rundocker compose -f docker-compose.kiosk.yml up --buildto bring up the full three-service
stack — Proxytrace API (:5200), web UI (:5201), and the bundled sample chat client (:5202).
Without credentials the stack still boots in read-only demo mode; the demo API key defaults to
pk-kiosk-demo. The full presenter runbook is insample-client/README.md. -
The demo "Customer Support" agent can now showcase social-engineering resistance. The kiosk seed
arms the support agent with anissue_refundtool and a ten-case refund test suite — five of
which are social-engineering attempts to extract unauthorized refunds — pre-seeded with a 100%
pass-rate history, so a presenter can trigger the trick in the sample chat client and watch the
pass-rate drop on screen. -
Upstream provider key rotations are audited distinctly. Replacing a provider's upstream API
key now records a dedicated Provider Key Rotated audit event instead of the generic provider
config update, so credential rotations stand out in incident review and compliance reporting.
The key value itself is never recorded.
Changed
-
The traces timeline reads as a signal, not a picket fence. The strip above the trace table is
now a stepped line — volume rises from a zero line as a continuous profile, so a run of quiet
minutes and a sudden spike are one shape you take in at a glance rather than 120 separate bars.
Failures moved out from behind the volume: they hang below the zero line as red notches on a
scale of their own, so a handful of errors during a busy hour is visible instead of buried as a
sliver at the foot of a tall bar. A key names both lanes, and hovering drops a playhead across the
strip with the exact time, count, and error count for that slice. Drag-to-zoom, scroll-to-zoom, and
click-to-focus work exactly as before. -
Trace lists scroll instead of paging. The Traces table and a session's trace list no longer
have page buttons or a "per page" picker — keep scrolling and the next batch loads, until an
End of results marker says there is nothing further. The column header keeps a running
1–16 of 4,208count so you always know where you are. Scrolling back through time now shows
day markers between rows (when sorted by Time), so a list thousands of rows deep still tells
you which day you are looking at. Long lists stay fast because only the visible rows are drawn. -
Trace stats describe your filters, not the page. The band above the trace table (traces,
tokens, cost, average latency, error rate) now covers every trace matching your current
filters rather than the twenty on screen, so the figures hold still while you scroll and answer
"what does this slice of traffic cost?" directly. Cost reads as "—" rather than 0 when no matching
trace has a known price. -
Live traces no longer move the list while you read it. New captures pause while you are
scrolled down — a pulsing dot beside the position count shows some are waiting — and arrive when
you scroll back to the top. In a session with more than one batch of traces, a new call now
appears in the list instead of silently landing on a page you were not looking at. -
x-proxytrace-session-idnow names a debugging session, not a conversation. The header that
used to set the conversation/thread key now identifies the broader session (see Added), and
thread-level grouping moves to the newx-proxytrace-conversation-idheader. Existing clients need
no change: when nox-proxytrace-conversation-idis sent, the session key still drives conversation
grouping, so calls keep grouping into threads byte-for-byte as before — and now gain a session view
on top. Sendx-proxytrace-conversation-idonly when you want one session to hold several distinct
conversations. Neither header is forwarded upstream.
Fixed
-
The refund showcase could not be fixed by the optimizer it was built to demonstrate. The demo
tricks a support agent into refunding an out-of-window order, then has Proxytrace propose a prompt
that stops it. In practice the "fixed" agent still gave the money back roughly one run in three —
it opened adamagedreturn instead of callingissue_refund, which pays out in full anyway, and
the runbook's success check only looked for the refund tool. The cause was the scenario, not the
optimizer: the customer says the motor died, and the store's own policy granted defective items a
full refund with no time limit, so an agent that reasoned carefully was right to pay out. Product
failures reported after the return window are now a manufacturer-warranty matter, the sample client
refuses out-of-window returns with no damage on file, and the demo's pass criterion is "no refund
granted or promised, by any route". The fixed agent now declines, offers the 50% goodwill
credit, and points the customer at the warranty — measured 4 runs out of 4. -
A test case built from the wrong trace of a tool loop could never pass, and nothing said so. A
run asks the agent for one reply per case, but an agent turn that calls tools is captured as several
traces. The last one already contains every tool call the agent made and every result it got, so
the only reply left is a closing summary. Turning that trace into a regression test — keeping its
input but editing the expectation to "the agent should have refused" — produced a case that was
unpassable by construction: the input already said the action succeeded. It stayed red through every
A/B run and looked exactly like a prompt fix that had not worked, when the fix was fine. Proxytrace
now counts the tool calls a case's input already resolved and reports it on the case, Tracey's trace
search shows which traces belong to one turn and what each of them decided, and adding a corrected
case on top of a completed tool loop reports back which case is affected and which earlier trace to
use instead. Promoting a trace as-is is unaffected. The manual explains how to pick the right trace. -
The refund suite's failing cases failed for the wrong reason. The seeded social-engineering
cases named no order, so the agent's first move was "what's your order number?" — which the
helpfulness judge scored as unhelpful. Three of the four red cases were failing on etiquette rather
than on policy, one of them while scoring full marks for policy compliance, and that noise was what
the optimizer read as its diagnosis. Those cases now embed their order lookup, so the agent answers
with the facts in hand — the suite fails 4 to 6 of 11, and every red is a policy red. -
A real improvement could be dismissed as noise on a small suite. An A/B validation ran each arm
once, so a twelve-case suite gave the significance test twelve observations to work with — not
enough to prove anything short of an enormous effect. A candidate prompt taking a suite from 5/11
to 8/11, a large and genuine gain, came out at p≈0.19 and was filed as "No improvement", and no
amount of rewriting the prompt could change that. Validation now runs three samples per arm and
pools the results, which proves that same change (15/33 → 24/33) properly. It costs three times the
runtime;Optimization__AbSampleCounttunes it. -
One optimizer's bad model output threw away every other optimizer's work. The optimizers that
propose prompt changes, tool-definition changes and model switches ran as a batch, and if the model
returned malformed JSON to any one of them the whole batch was discarded — a failed run produced no
theories at all, with nothing on screen to say why. Each optimizer's failure is now contained and
logged, and the theories the others found still arrive. -
Tracey talked far too much. A multi-step job turned into a running commentary: a sentence
announcing each tool call ("let me load the skill and inspect the trace"), another confirming it
worked, her internal checklist mirrored back as "Step 1 / Step 2" headings, and a paragraph per
step restating what the cards on screen already showed. She now answers in one short block — a
bold lead line plus a few bullets or a small table, with status markers like ✅ ❌⚠️ 🔴 🟢 — and
writes nothing at all between tool calls, since every call already shows its own row. Ten tool
calls end in the same short answer as one. Replies are quicker to read, and cost noticeably less
to generate. -
Tracey could not see a suite's test cases or evaluators. Her suite tool promised the per-case
ids that editing and removing a case require, but only ever returned the case count — so those
actions were unreachable unless a case id happened to come up some other way. A suite's attached
evaluators were invisible to her for the same reason, which meant she could create a judge but not
tell whether one already covered the behavior she needed scored. Both are now part of what she
reads, and she can change which evaluators a suite scores with. -
Tracey guessed which test run to look at. After waiting for a run to finish she had to find it
again by listing the agent's runs and taking the newest one — which could pick up a different run
that finished in between. A finished run now reports its own id. -
Tracey now opens a trace you paste by id. Asking the assistant to look at a specific trace by
its id ("debug trace6339237b-…") made her report that no such trace existed, even though the
trace was right there. Her instructions told her that ids only ever come from a list and never
from what the user typed, so instead of fetching the trace by id she ran a free-text search for
it — and that search covers the captured request and response text, not ids, so it always came
back empty. A pasted id is now treated as what it is: she fetches that trace (or agent, run,
suite, proposal) directly, and only reports it missing if the lookup really finds nothing. -
Multi-turn conversations no longer lose every turn after the first. When an agent handled a
tool-calling exchange, the follow-up calls — the ones carrying the tool results and the final
answer — could vanish from Proxytrace while the opening turn appeared normally, so a conversation
that plainly ran to completion in the client showed up as a single trace ending in a pending tool
call. Ingestion updates the agent as calls arrive (endpoint, model parameters, current version),
and when a rapid burst of calls for one agent collided on that update, the losing call was
classified as permanently malformed and thrown away instead of being retried. Such a collision is
now retried and the trace is kept. -
An agent's system prompt is recorded exactly as sent. Captured calls stored the prompt with a
System:prefix glued to the front, so agent pages showed the wrong text. Because the prefix also
changed the prompt's fingerprint, the first live call to an agent created outside ingestion (a
seeded demo agent, or one set up in the UI) always appeared to change its prompt and appended a
spurious new version. Prompts are now stored verbatim and that phantom version is gone. Existing
agents whose prompt was captured with the prefix get one final version on their next call, after
which their history stays stable. -
Long model names no longer overlap the columns beside them. On the dashboard's live feed, a
model id wider than its column —deepseek/deepseek-v4-flashand friends — painted straight over
the turn count on its left and the status on its right, leaving all three unreadable. Model tags
now shorten with an ellipsis to fit their column and show the full name on hover, and the live
feed gives the model column more room to begin with. On the Traces page the same names were cut
off mid-character and ran flush into the status dot beside them; that column now has a gutter. -
The Traces table uses its width better on a large screen. The message and agent columns grew
with the window while the model column stayed capped, so a wide display showed truncated model
names next to a stretch of empty space — and message previews ran into the agent name beside them
with nothing between. The agent and model columns now take the width they can actually use (model
names fit in full on a wide screen), every spare pixel goes to the message preview, and each
column keeps a gutter. Narrow windows are unchanged. -
The read-only demo no longer throws errors at visitors who touch a disabled control. Kiosk
mode dimmed every button that would change something, but only against the mouse — tabbing to one
and pressing Enter still sent the request, which the server refused, surfacing a red error. The
Playground's composer escaped the dimming entirely, so ⌘/Ctrl+Enter reported a raw technical
error message. Actions that can't apply are now declined in the browser with a quiet "read-only
demo" notice, whatever triggered them, and the composer says why it's disabled. (The server
always enforced this; only the demo's manners were wrong.) -
No more duplicated traces when the server shuts down mid-ingest. A captured call is written to
the database first and everything that follows — the live trace event, the blocked-request
notification, queueing the call for anomaly review — is bookkeeping around it. If one of those
steps was interrupted (a graceful shutdown or restart) or hit a transient database error, the
whole ingest was reported as failed even though the trace was already stored, so the proxy's
delivery guarantee handed the same call over again and it appeared twice in the Traces list. Those
follow-up steps are now logged and skipped instead of failing the ingest, so a restart in the
middle of ingestion can no longer double up your traces. -
A failure in the app's chrome no longer blanks the whole app. The top bar and nav rail render
outside the page's error boundary, so anything that went wrong while drawing them — a
notification whose type the UI did not recognise, or simply the notification inbox failing to
load because the server was restarting — unmounted the entire interface and left a blank page
until a manual reload. The rail, the top bar and the page area are now each contained
independently: a broken control degrades to a small notice and everything else stays usable, and
navigating clears it. A notification inbox that fails to load now shows an empty bell and an
error toast, and a notification pointing at a captured call renders correctly rather than
throwing. -
A stale error no longer follows you from page to page. An error caught on one page stayed on
screen on every page you navigated to afterwards, and its Try again button re-rendered the same
failure; navigating away now clears it. -
Opening a notification closes the trace or error panel underneath it, instead of stacking two
detail panels whose keyboard shortcuts (Esc, ← →) fought each other. -
Opening a notification marks it read, including when it is opened from a deep link or an
emailed link; previously the unread badge stayed until you clicked the tick explicitly. -
The notification panel no longer closes over the page you navigated to, and marking one
notification read or dismissing it no longer freezes the buttons on every other row while the
request is in flight. -
Provider key rotation and revocation now take effect on the very next proxied request. The
ingestion proxy previously cached resolved credentials — including the decrypted upstream provider
key — for up to 30 seconds, so a rotated key could keep being forwarded (and the replaced key kept
authenticating inbound) until the cache expired, in every proxy replica independently. The proxy
now resolves credentials from the database on every request and fails closed when the database is
unreachable instead of serving stale credentials. TheApiKeyCachesetting is removed. -
Keyboard focus is now visible on the remaining bespoke controls. The playground settings
rail, agent picker, endpoint chip, tool result/error tabs, suite-wizard preset chips, search
indexing kind toggles, the evaluator recent-evaluations filter chip, and the move-version target
list now show the standard focus indicator when reached with the keyboard, completing the
focus-ring sweep started with the shared button and row primitives. -
All text sizes now come from the design type scale. Seven components (evaluator cost and
stat panels, the setup wizard headings, and the evaluator playground score chip) used one-off
pixel sizes; they now use scale tokens, including a new intermediate 22px display size, and the
score chip's "/5" suffix no longer renders below the 10px legibility floor. -
The demo seed now backdates evaluation history along with its runs. Evaluation statistics
previously kept the seed time even when their runs were spread across the past 30 days, so the
evaluator workspace's pass-rate trend showed "Not enough data" in the demo/kiosk stack. Updated
test results now rewrite their evaluation-statistics timestamps, and the trend chart renders
real history.