Skip to content

Proxytrace 1.9.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 20:07
· 73 commits to master since this release
Immutable release. Only release title and notes can be modified.

Fixed

  • The documented image address works again. Every published reference to the container image —
    the compose file shipped in the release artifact, the one-line docker run in 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 to SyntaktikEU. Pulls against it fail with owner 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 canonical ghcr.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 at SyntaktikEU/Proxytrace instead 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 .env template all
    told new users to point their agents at http://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 trace 5b71… 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-id header 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-Agent attribution 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. Copy kiosk.env.example to .env, fill in your credentials,
    and run docker compose -f docker-compose.kiosk.yml up --build to 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 in sample-client/README.md.

  • The demo "Customer Support" agent can now showcase social-engineering resistance. The kiosk seed
    arms the support agent with an issue_refund tool 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,208 count 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-id now 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 new x-proxytrace-conversation-id header. Existing clients need
    no change: when no x-proxytrace-conversation-id is 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. Send x-proxytrace-conversation-id only 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 a damaged return instead of calling issue_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__AbSampleCount tunes 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 trace 6339237b-…") 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-flash and 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. The ApiKeyCache setting 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.