Skip to main content

Platform updates — Detectors settings

Released: November 2, 2026
  • Settings → Detectors — turn each detector pack Off, Preview or On for a project (On asks for confirmation, because findings a pack proves then show to your team and can become Issues), and confirm what your own tools do so a pack’s checks can reach proven. Each tool shows its suggested role and what confirming changes, Not this rejects a suggestion, a risky role is confirmed one tool at a time, and a tool whose definition changed shows Changed since you confirmed until you confirm it again. A Fields card maps where a pack reads values such as an order total. Owners and Admins only; part of the new dashboard interface. See Detectors.
  • 14 new checks, in preview — four each for customer support (refund above the order, account change before an identity check, dispute not passed on, required statement missing), marketing (campaign currency, frequency cap, tracking links, duplicate audiences) and data analysis (queried period, top-N lists, stale data called current, rates that do not match their inputs), and two for AI products (discount and tax figures, adverse decisions without their reason). They stay in preview, whatever the pack’s mode, until each passes its accuracy tests. See New checks in preview.
  • Four new detector packs: E-commerce, Sales and CRM, Scheduling, Coding agent — on by default with every check in Preview, so nothing reaches Issues until each check passes its accuracy tests. They check stock and prices against product records, delivery promises, order totals, deal stages, the contact a message went to, duplicate records, booked times across time zones, overlapping bookings, “tests pass” claims against the test output, and files a coding agent says it changed. Issues gain the matching Type labels. See Four more packs.
  • New and extended routes — GET /v1/projects/{id}/functional-packs, PATCH /v1/projects/{id}/functional-packs/{pack}, GET and PATCH /v1/projects/{id}/functional-mappings/fields; the proposals response now includes packRole, lifts, changedSinceConfirm and previousRole. See the API reference.

Platform updates — Capture v2 on by default, PII tokens in the SDK, and Capture coverage

Released: November 1, 2026
  • Capture v2 is on by default — TypeScript SDK 1.6.0 and Python SDK 1.7.0 record tool results, tool definitions, the structured messages and system prompt a model saw, requested tool calls, the output schema and agent input and output text unless you turn it off: captureV2: false / capture_v2=False, or ZESPAN_CAPTURE_V2=off (the environment variable wins over the option). storePrompts and storeToolIO still decide what is stored. Apps pinned to an older SDK are unaffected, and a one-time notice is logged when it is on only because of the default. See the TypeScript and Python SDK pages.
  • Tool payloads are kept up to 32,768 characters (was 10,240), with a result summary — a cut result carries zs_tool_summary (keys, paths, numbers, id-like values and list counts, at most 4,096 characters; dropped when tool IO capture is off). A self-hosted API older than this release cuts at 10,240, so upgrade the API. See PII redaction.
  • SDK PII redaction is on by default — redactPii / redact_pii now replaces emails, phones, cards, IBANs, SSNs, national ids, IPs, street addresses and dates of birth with keyed tokens such as [EMAIL_3F9A2C1B] inside your app before sending. The same value gives the same token within a trace under one key (one SDK process, or services that share a redactionKey), and tokens are not reversible. The server tokenises with its own key, so its tokens differ from the SDK’s; checks treat tokens from different keys as unknown, never as different values. Pass false to turn it off. Secrets and sensitive keys are always removed, and the server also redacts content fields (identity fields such as user_id and session_id are stored as sent, so keep emails out of them). New option redactionKey / redaction_key shares a key across services (random secret of 32 bytes or more; never log it). Redaction has a CPU budget of about 10% of one core and can hand a span to the server under load, or in TypeScript when the in-app pass fails (the span is marked zs_capture.pii = "server"). Cut fields (including tags over 64 characters) are cut at safe boundaries, so a field can be up to 64 characters shorter than the cap. See PII redaction.
Self-hosted: deploy this API release before your apps upgrade to TypeScript SDK 1.6.0 or Python SDK 1.7.0, and do not roll the API back below it afterwards. An older API compares SDK tokens from different keys as different values (false identity mismatches), treats each app restart as a new system prompt version, and drops the SDK’s token features. To roll back anyway, first pin the previous SDK (TypeScript 1.5.x, Python 1.6.x) or set redactPii: false / redact_pii=False. See PII redaction.
  • Confirmation hashes are unchanged by the new default — they stay plain unless you set redactPii: true (redact_pii=True) explicitly or turn tool IO capture off, in which case they are keyed.
  • Bigger SDK requests, a bounded queue, fixed retry rules and lost-event reports — TypeScript SDK 1.6.0 and Python SDK 1.7.0 send up to about 3.5 MB of events per request before gzip (was about 900 KB; the API now accepts 4 MiB after decompression, was 1 MB, and OTLP /v1/traces accepts 8 MiB). A self-hosted API older than this release answers 413; the SDK halves its request size and keeps working, so update the API first. The send queue is bounded at 10,000 events or 20 MiB in both SDKs (maxQueueSize / maxQueueBytes, max_queue_size / max_queue_bytes; the Python default queue was 1,000). An event over 1 MB is trimmed (its largest content field is cut, and zs_capture.trim: "event_size" is set when the event has a metadata object), then dropped if it still does not fit; an event whose size estimate is over maxQueueBytes / max_queue_bytes (20 MiB by default) is dropped when queued without trimming. Network errors, 500, 502, 503 and 504 are retried with backoff (or the Retry-After wait, at most 60 seconds), a 429 pauses sending for Retry-After (at most 60 seconds), and any other answer is not retried. See Delivery, retries and lost events.
  • Events lost in the app on Capture coverage — the SDKs count events they drop, by reason, and send counts only (never content) to the new POST /v1/sdk/client-reports. Capture coverage shows the total and reasons, or “none reported”, or “unknown” when no SDK has reported, or “could not be read”. The capture-coverage response gains a lost object. Needs TypeScript SDK 1.6.0 or Python SDK 1.7.0 or later and an API with the route. See Events lost in the app.
  • Capture coverage on Instrumentation — a new card shows, for five kinds of input the checks need, the share of your last 7 days (or 24 hours) of traces that lack it, with the step that fixes it. A read failure shows an error with a retry, not “no traces”. See Capture coverage. A tool result of 9,984 characters or more with no SDK marker (older SDKs, OpenTelemetry) is treated as cut; with capture v2 off, SDKs from TypeScript 1.6.0 and Python 1.7.0 mark every cut. The Older SDK version row counts only traces from SDK versions before TypeScript 1.6.0 or Python 1.7.0, so its advice (upgrade) always applies. The Instrumentation page is part of the new dashboard interface.
  • zespan doctor PII check states the real layers — it no longer suggests redactPii: true or says server-side redaction cannot be turned on: the SDK redacts PII by default (TypeScript 1.6.0, Python 1.7.0), the server always redacts content fields, and the project setting adds extra detection. See Doctor.
  • New endpoint GET /v1/projects/{projectId}/capture-coverage — counts per gap for a window of 1 or 7 days; 503 with capture_coverage_unavailable when it cannot be read. See the API reference.
  • Ingest can answer 503 ingest_overloaded with Retry-After — when the live ingest queue is deep or old, or its Redis is near its memory limit, /v1/ingest and /v1/traces may shed new requests instead of growing the backlog. Nothing is stored for a shed request; the SDKs already wait the Retry-After and retry. Self-hosted: this is off unless you set INGEST_BACKPRESSURE=on. See the API reference.
  • Redact personal data (PII) no longer redacts person names — name detection replaced too many product, model and place names, so the project setting now leaves names as they are. Emails, phone numbers, card numbers and the other kinds are still redacted. Self-hosted: PII_OPENREDACTION_NAMES=on turns name detection back on. See PII redaction.
  • One malformed field no longer fails a whole ingest batch — typed fields that arrive in the wrong shape (tags that are not a map, eval scores that are not numbers, tool lists that are not lists, event ids that are not UUIDs, unreadable timestamps, a non-text environment or model) are corrected or dropped for that event, and the rest of the event is stored. Before, one such value could cause the whole batch to be rejected.

Platform updates — A glossary, page guides, next-step prompts and a shorter tour

Released: October 31, 2026
  • View an evidence pack in the app, and a redesigned glossary — on Compliance, every completed pack now has View as its main action (Download and Verify stay). It opens the pack at /compliance/packs/{packId} with the period, when and by whom it was generated, a Contents list that scrolls to a section, the full SHA-256 with Copy hash, Verify with the result shown on the page, Download and, for an HTML pack, Print (the document alone, without the app). The document is shown, and printed, from a locked-down frame with no scripts, forms, network access or access to your session; when the server cannot read the stored file the page says so and offers Retry instead of showing a blank frame. New query ?view=true on GET /v1/projects/{projectId}/evidence-packs/{packId} returns the document content inline (up to 10 MB; above that the response sets tooLarge and the page offers Download), and each view is recorded in the audit log as compliance.pack_viewed. The Glossary page now has a search box that filters as you type (press / to focus it), an A to Z bar, terms grouped by area, a card for each term with Open and Docs links, and #{id} links that scroll to and highlight a term; the definition popup now leads with the short definition and ends with See in glossary and Read more — see Evidence Packs → Viewing a pack in the app and the Glossary
  • A trace with a silent failure is never called Healthy, and the trace page shows where it broke — when a functional check found that a run went wrong without any span erroring, the trace’s verdict now reads Completed, but a silent failure was found: (or a possible issue when Zespan could not prove it, never claiming proof), the header tag reads Silent failure or Possible issue, and View issue links to the issue when one exists. The same verdict is used by the trace list (a row with a finding no longer reads OK), and the MCP zespan_trace_verdict_get tool. A new Where it broke panel under the verdict (only when a span failed or a finding was raised) shows, for each place first failure first, the Stage (Instructions, Model, Retrieval, Tool, Agent logic, Guardrail), the Span (click to select it), the Model, the Prompt (name and version, linked to that version) and Observed, quoted from the span’s error or the check’s evidence; a missing prompt says Prompt not linked and a missing model says so, nothing is guessed. The waterfall rows show their stage, failing spans and spans a finding cites are highlighted, and a span’s Facts list its Stage, Model and Prompt. While no evaluator has scored a run, the three empty lens cards are replaced by one line with Run an evaluator, so the page reaches the waterfall sooner. A model call now carries its prompt name and version whenever the SDK recorded them (load the prompt with prompts.get() and compile() in the same trace, in TypeScript and Python). An errored trace now shows a Cause and Fix under Where it broke (the trace’s issue first, then its own saved analysis, then the automatic classifier; with none known it says No cause found yet), and every trace, successful or not, lists its issues, visible findings and technical signals (a retried or slow tool) under Issues on this trace, each with its type, cause, first fix step and a link to the issue. Findings stay hidden from everyone but owners and admins while your functional detectors run in the default shadow mode — see Traces → Trace detail and Prompt management → Link calls to a prompt version
  • Functional and domain-pack issues now appear on real projects, and the Issues list has a Type column and filter — when your functional detectors are on, the findings a check proved from the stored spans (a tool called that was never defined, a total that is not the sum of its lines, a refund time the policy contradicts, a card number in a reply, a figure that does not add up, a campaign created twice) become issues on the Issues page, one per check and agent, with a cause Verified from spans, a fix and a prevention. Weaker findings (“likely” or “signal”) are never turned into issues, and nothing changes while the detectors stay in the default shadow mode. The list gains a Type column right after Verdict (Functional, Technical, Support, AI product, BI, Martech, empty when Zespan cannot say) and a Type filter next to Verdict; the small kind tag under the operation is gone from the list and stays in the issue page header. The Remediation column is now one short line (Auto fix, Auto fix · AI pending, Auto fix · AI off, Auto fix · Pro for AI, Auto fix · Demo) with the full reason on the issue page, and an issue raised by a detector reads Auto fix instead of waiting for an AI analysis. Zespan does not open an incident or queue an automatic AI remediation for these issues on its own. Erasing an end user’s data now also removes their traces from such issues — see Issues
  • A clearer issue page — the Root cause card now has a proof badge, one paragraph, an evidence line that reconciles the counts (“12 occurrences. Found 2 matching failures in 2 retained traces; 3 are needed to confirm, so this cause is not confirmed yet.”), and Fix and Prevent as two numbered lists. Times show as relative times with the exact local time on hover, never a raw timestamp. A Fix list has Open sample trace; a Prevent step that sets up an alert on a metric Zespan alerts on has Add alert (opens the new-alert dialog prefilled with that metric, for you to save) and one that adds a guardrail has Create guardrail. Suggested remediation no longer repeats those steps: it appears only for an AI remediation, an applyable fix, a real queued state, or one line saying why the AI analysis is not available with the action that fixes it. The header drops its ”—” placeholders and shows the agent when one was recorded. On the shared demo the Demo story panel now opens as its small pill on any page that is not one of the story’s pages. See Issues → Issue detail page
  • Glossary and in-product definitions — product words such as trace, span, issue, incident, evaluator and guardrail now have a plain-language definition. Click an underlined word on a page to read a short definition and jump to the full entry, or open Glossary from your account menu or the command palette. The full list is also in the Glossary
  • “Come here when” line under each page title — each main project page now says what it is for and when to use it, for example Traces: “Come here when a user says the AI did something wrong.”
  • Empty pages now teach — a page with no data (for pages with a date range, none in the last 90 days) now says what feeds it and offers one next step instead of a blank panel. Users, Sessions and Tools show a ready-to-copy TypeScript and Python snippet; Costs, Errors and Issues point to where their data comes from; Agents, Models, Performance, Incidents, Alerts, Changes, Evaluations, Guardrails, Policies, Simulations, Datasets, Annotations, Prompts, Cost attribution, Forecasts and Value each name their data source and offer one action. While a page shows this state, its header no longer repeats the same “create” button
  • Next-step prompts after you create or run something — deploying an evaluator, queueing a retroactive evaluation, creating a guardrail, applying a policy, creating an alert rule, creating a dataset, publishing a prompt version to production, and starting a simulation run now confirm what happens next and link to where you will see the result
  • The product tour is now three steps (Overview, an issue’s cause and fix, ZespanPilot) and skips anything your plan or project doesn’t have — a step whose target doesn’t appear within 3 seconds is skipped instead of retrying behind a dimmed screen, and the tour closes quietly if nothing is left to show. It is offered once as a toast after your first trace (it never starts by itself) and is always available from Take the tour in the account menu — see Overview
  • Compact sidebar with a cleaner shortcut tooltip — rows are now 28px and group headings 24px, so all 21 rows and every group heading fit on a 1440×900 screen with your account and footer in view. Hovering a row opens a short tooltip to its right (page name plus one key chip such as G T) instead of below it; only one tooltip shows at a time, and the pin button no longer overlaps the count badge. See Navigation
  • Explore the shared demo while signed in — choose Explore the demo in the account menu, or See it on demo data on a page that has nothing to show yet, to open a populated sample project with your own session. It is read only, shows only you and a fictional team (other people who opened it never appear), appears in the switcher as Demo (read only) without counting toward your 3-organization limit, never becomes your default organization, and has a banner with Back to my project. New endpoint POST /v1/demo/join — see Quickstart → Explore the demo and the API reference
  • A fixed 21-row sidebar in five groups, related pages as tabs, and a “Set up” chip — the sidebar now has five groups (ZespanPilot, Observe, Fix, Improve, Govern) with 21 rows that never move, hide or change group on their own. Pages that belong together share a row with a tab strip: Costs (Explorer, Attribution, Value, Forecasts), Issues (Issues, Errors), Test lab (Simulations, Datasets, Annotations) and Prompts (Prompts, Playground). Govern holds Policies, Compliance and Audit log, each its own row; Policies and Compliance still show their plan name when your plan does not include them. Settings and Instrumentation are under the gear next to your account at the bottom of the sidebar. Every page keeps its address and its place in the command palette. A row for a feature you have never used in the project (Sessions, Users, Tools, Costs, Evaluations, Guardrails, Alerts, Test lab, Prompts) shows a small Set up chip and a quieter label; it stays where it is, never returns once the feature has been used, and is left off whenever Zespan cannot tell. Saved pins and collapsed groups carry over: a pin on a page that became a tab moves to its row, a pin on Policies, Compliance or Audit log stays pinned, and Govern starts open. Sidebar preferences saved in your browser are migrated the first time you open the dashboard. The Trust, safety, risk or compliance answer to “What do you mostly do?” pins Guardrails, Policies and Compliance. New endpoint GET /v1/projects/{projectId}/feature-state (read access to the project) returns which features the project has set up — see Finding your way around the dashboard
  • Locked pages now teach, and Annotations is Pro everywhere — a page your plan does not include (for example Evaluations, Alerts, Simulations or Incidents) shows what the page is for, what the plan gives you, an outline of its layout drawn as empty bars (captioned Example layout. Your data appears here after you upgrade., with no sample data) and one Upgrade to button, with Compare plans beside it (members who cannot change billing get Compare plans as the main action and a note to ask an Owner or Billing member). A smaller feature inside a page keeps its short upgrade notice. Annotation queues now need the Pro plan on the page, the sidebar and the API: creating, renaming, deleting and populating a queue returns 403 PLAN_REQUIRED below Pro (it was Team before for the API), while reading queues is unchanged — see Billing → What a locked page shows and Annotation queues
  • Trace detail and Evaluations now show the answer first — a trace opens with the verdict, a one-paragraph what happened written only from the trace’s own data (no AI call, and it never states a cause the data doesn’t show), the failing span with Open span, and then the three lenses. The inspector strip is now Overview, Input/Output, Timeline (was Log stream) and Root cause; Evaluators, Agents, Retrieval, A2A, Metadata and Session are under More; Evaluators is there whenever the lens cards are (so a lens card always opens it), and the others appear only when the run has data for them. The keyboard bar is replaced by Press ? for shortcuts, and the shortcuts list opens with This page. ?tab=<id> opens any tab, including those under More. On Evaluations, the status band, the “connect an LLM provider” notice and the “could not run” notice are now one status line (actions of the folded-in facts, such as Connect a provider, stay as second buttons), followed by the vitals and the list. A healthy trace’s verdict no longer shows $0 or 0ms when cost or duration was not recorded — see Traces → Trace detail and Evaluations → Reading evaluation results
  • ZespanPilot guides you through setup — the Pilot page now lists three orientation questions (“What should I set up first?”, “What is the difference between issues and incidents?”, “Why is this page empty?”). Pilot answers them from your project’s real set-up state (SDK, session and user ids, one evaluator, one alert rule, guardrails, in that order, with the first missing step and a button to its page) and from the Glossary, the same text the dashboard shows. A step Zespan cannot check at that moment is reported as not verified, never as done. A page with nothing to show and a locked page now have an Ask ZespanPilot link next to their main action; on a locked page it asks what the page is for, answered from the page’s own description. Pilot can also open every project page (Issues, Policies, Annotations, Value, Changes, Compliance, and pages under Settings) and link straight to an issue or a policy — see ZespanPilot → Getting oriented
  • One optional question at the end of onboarding: “What do you mostly do?” — pick Building the AI app or agent, ML / data science, Product manager, On-call / SRE / platform, Trust, safety, risk or compliance, Finance / FinOps, Founder or executive, or leave the default Skip. Your answer only sets first-run defaults: the page Zespan opens on when you finish (Traces, Evaluations, Overview, Issues, Guardrails or Costs) and up to three pinned sidebar rows. Executives get a See the value report link in the Overview header. A sidebar you have already saved is never changed, a returning user sees no difference, and the answer never hides a page or limits access. Change it any time under Profile → How you use Zespan. New endpoint PUT /v1/user/persona, and GET /v1/user/profile now returns persona — see Profile and Quickstart
  • ZespanPilot works in the shared demo, with sample questions only — Pilot is now in the demo’s sidebar and Ask Pilot button (⌘J). It lists starter questions (including the three orientation questions) and answers them from stored text written from the demo’s own data, so it is identical for everyone and uses no AI. Any other message is answered with “Sign up or open your project to ask your own.” Demo conversations are not saved, so visitors never see each other’s questions. The product tour on the demo now includes its ZespanPilot step. Everywhere else Pilot is unchanged — see Quickstart → Explore the demo
  • A guided “demo story” on the shared demo — the demo now opens with a Demo story side panel that follows one silent failure in five steps: the Overview, the issue, its proven cause, the fix and the prevention. The failure is RefundAgent calling check_refund_status with the same order id again and again, getting the same “pending” result and never answering, with no span errors. The cause shown on the issue is computed from those spans by the same check every project uses, not written by hand. The panel is not a tour: nothing is dimmed, you go at your own pace, a step gets a tick when you reach its page (or its part of the issue page), it opens once and can be reopened from Demo story in the demo banner or the account menu. On the demo it replaces the 3-step tour toast (the tour stays under Take the tour). The story is recreated with current dates every day, and ZespanPilot’s stored demo answers for “Why did agent success drop this week?” and “What is the difference between issues and incidents?” now mention it — see Quickstart → The demo story and Issues
  • Sidebar shortcuts in a tooltip — a row’s keyboard shortcut moved from two small boxes on the row into a tooltip that opens after about half a second on hover or keyboard focus (Traces · g then t), so a hovered row shows only its count and the pin button; the narrow icon-only sidebar uses the same tooltip for the page name. P on a focused row still pins or unpins it — see Finding your way around the dashboard
  • See the policies Zespan proposed — when the prevention layer is on for your project, the Policies page has two views, All and Proposed by Zespan, each with a count. Proposed by Zespan lists the policies Zespan drafted from a finding and tested on your recorded traces (dismissed and not-proven drafts are never listed), and each of those rows carries a Proposed by Zespan link to Where this came from on its policy page — see Policies → Policies Zespan proposed
  • The shared demo shows no create buttons it cannot use — in the read-only demo, buttons that create or run something (Author a policy, Generate evidence pack, Add guardrail, Create alert, New evaluator, New dataset, New scenario, New prompt, New queue, Generate or Apply a fix, Run retroactively, Run on a prompt, Create rule or Dismiss on a suggested guardrail rule, and the matching buttons on empty pages) are replaced by a Sign up to … link for the guest, or a short Read-only demo note for a signed-in person who joined. In the Playground nothing runs against a model: Run, Run all and chat Send are turned off and the page shows the same link or note. Navigation and read actions are unchanged. The Demo story panel can be minimized to a small Demo story · n of 5 pill, the Overview no longer opens the “What should this page lead with?” question in the demo, and the ZespanPilot footer in the demo says “Sample answers only. Nothing runs against a model.” — see Quickstart → Explore the demo
  • The shared demo now has policies, a prevention draft and an evidence pack — the demo’s Policies page shows an applied policy (“Refund agent: keep payment data out of replies”) with its guardrail, test and apply record, and, when prevention drafts are switched on for the demo, a drafted repeat guard for the demo story’s RefundAgent loop (“Stop repeated identical tool calls”), proven on RefundAgent’s recorded runs and never applied. Compliance shows one SOC 2 evidence pack for the last 30 days, listing only the fictional team, and the Audit log has the matching entries. They are built by the same code a project uses and recreated daily; the demo never makes a model call to build them. ZespanPilot’s stored demo answers for “How do I stop a delegation loop from happening again?” and “What should I set up first?” now name the policy (and the drafted repeat guard, when drafts are on) — see Quickstart → Explore the demo
  • Issues now say what kind they are — the Issues list and the issue page show a small tag: Technical (the call failed or misbehaved: an error, a retried or slow tool, the latency SLA evaluator), Functional (the run finished and the agent did the wrong thing without an error: a loop, a repeated user question, a failing built-in quality, safety, agent or RAG evaluator, a failed functional check) or the name of a domain pack (Support, AI product, BI, Martech) for an issue raised from one of that pack’s checks (in the shared demo today). Zespan works it out from the issue itself, so it needs no setup and applies to every project; an issue that does not say its kind (a custom evaluator, the cost budget evaluator, an older degraded issue with no signal) gets no tag rather than a guess. GET /v1/projects/{projectId}/issues and the issue detail response now carry it as kind ({ id, label }, or null) — see Issues → Kinds of issue
  • An issue whose fix was proven from its spans no longer reads “Analyzing…” — an issue with no classifier cause but a high-confidence cause proven from its spans (an agent loop, for example) now shows Auto fix in the Remediation column, because its fix steps already exist on the issue page; an issue with neither still reads Analyzing…
  • The shared demo’s Issues page now shows a mix of kinds, each proven from its own spans — when the functional checks are switched on for the demo, thirteen issues made from recorded runs of four fictional agents (CustomerSupportAgent, RefundAgent, and two new ones, ReportsAgent and CampaignAgent): four Technical (a 429 retry storm, a provider 503, a context that was too long, a model timeout), two Functional (a tool called that was never defined, a total that is not the sum of its lines) and seven in a domain pack (Support, AI product, BI, Martech). They are found by the same functional detectors, clustering and error-pattern proof every project runs, and an issue is shown only when the check proved it at its strongest level; the cause, fix and prevention on each are the check’s own wording, and healthy runs of the same agents are not flagged; with the checks switched off only the four Technical ones are shown. Nothing in them calls an AI model. The demo’s Pilot answers for “Why did agent success drop this week?” and “What is the difference between issues and incidents?” now describe the mix (naming the functional and pack issues only when the checks are on), and the demo story’s Overview step says it is the looping RefundAgent runs that end without an error — see Quickstart → What you see in the demo

Platform updates — Playground tool calls on every provider, consistent error and tool numbers, and evidence pack retry

Released: October 2026

SDKs

  • Image input metadata now covers the OpenAI Responses API, Haystack, Semantic Kernel and Google GenAI chat streams (TypeScript and Python) — responses.create (sync and async, streamed or not) is now traced as a model call in both SDKs (model, tokens, latency, errors, and with prompt storage on the input and output text), so a call that fails on an invalid input_image leaves an error span with its image metadata; guardrails and A/B model selection do not apply to it. This tracing is not behind the image switch: if you already use OpenAI autopatch (patch_openai in Python), Responses calls now appear as new model spans. When the OpenAI Agents processor also sees the call, only one event is kept. A streamed Google GenAI call (also chat.sendMessageStream) that the provider rejects before the first chunk, which is when an invalid image is rejected, now leaves an error span instead of nothing. In Python, the Haystack integration records image metadata when Haystack content tracing is on, and the Semantic Kernel integration reads a ChatHistory passed to a prompt function; both also record it on a failed call. Haystack components whose class name ends in Generator (such as OpenAIChatGenerator) are now typed as model calls, and Semantic Kernel prompt functions are now typed as model calls on current library versions (they were recorded as tool calls). These span types change with or without the image switch: Semantic Kernel prompt functions leave tool counts and tool alerts and count as model calls, and guardrails you passed to the Semantic Kernel integration now run on them. Google GenAI chat sendMessage was already covered. There is no TypeScript Haystack or Semantic Kernel integration. With the image switch off, no image metadata is recorded — see Capture fields
  • Inline images are no longer stored in prompt text (TypeScript and Python) — with prompt storage on, an image sent inline (a data:image/...;base64, URL, or the base64 of a PNG, JPEG, GIF or WebP file under data / inline_data) used to be stored as part of the prompt text, up to the 1 MB ingest limit. Both SDKs now replace the image bytes with [IMAGE_OMITTED] in every captured text field (prompts, completions, tool results, metadata strings) and keep the rest of the text and its structure. The image type stays (data:image/png;base64,[IMAGE_OMITTED]); parameters such as a file name do not. Credentials in pre-signed image URLs were already removed by the always-on secrets pass. Older SDKs still send inline images — see PII redaction
  • Image input metadata for invalid-image errors (off by default; TypeScript and Python) — with capture v2 on, capture_image_meta=True / captureImageMeta: true (or ZESPAN_CAPTURE_IMAGE_META=1; =0 always turns it off) records, for each image in a model request (also a failed call), only metadata: the declared type, the type read from the first bytes, the size, base64 validity, width and height for PNG, JPEG, GIF and WebP, the URL scheme and host (never the path or query), whether a pre-signed link had already expired, a hash for small images and the tool call that produced the image. Never the image or a full URL. Off, every event is unchanged. This lets Zespan tell a broken image from a rejection it cannot explain — see Capture fields
  • Capture v2: record what the trace checks read (off by default; TypeScript and Python) — capture_v2=True / captureV2: true (or ZESPAN_CAPTURE_V2=1; ZESPAN_CAPTURE_V2=0 always turns it off) makes the Google ADK callback handler, the Google GenAI wrappers (including the older Google SDKs), LangChain and LangGraph, LlamaIndex, OpenAI Agents, Claude Agent SDK, Vercel AI (TypeScript), Semantic Kernel (Python) and trace_tool / traceTool record tool results as JSON (an empty result kept and marked empty, a result cut at the limit marked as cut), and the ADK and GenAI paths also record the tool definitions a model was offered, the tool calls it requested with their ids, the conversation as structured messages, the system instructions, the output schema, and (ADK) the text of each agent span, with a content-free marker of how each field was captured. It follows prompt and tool storage and goes through the same redaction as stored prompts. Off, every event is unchanged. CrewAI, AutoGen, TypeScript Semantic Kernel and streamed GenAI calls are not covered yet. Python wrap_adk_agent is deprecated (it warns once) — see Capture fields and Google ADK
  • Tool definitions no longer leak into LangChain span metadata when tool storage is off — the model run’s invocation_params carried the tool schemas into span metadata even with tool input and output storage off (prompt storage on). Both SDKs now drop tools and functions from it in that case
  • Key redaction now covers tool results and arguments in every text form (TypeScript and Python) — a tool result recorded as a Python str(dict), or as JSON or a literal cut at a length limit, used to skip key-based redaction, so a password or token value inside it could be stored. Both SDKs now read these forms as a structure (never executing anything) or, when they cannot be read, replace the value of every matching key by text, before anything is cut or sent. JSON behaves as before, and a literal without a matching key is left as it came. Header-style pairs and key=value text (such as Config(api_key='...') or ?token=...) are covered too. The server applies the built-in key list again at ingest, also matching keys that end in a sensitive name such as PAYMENTS_API_KEY or db_password, so older SDKs are covered for new traces — see PII redaction
  • Python trace_tool never raises into your code because of a result it cannot record — a tool result that JSON cannot hold (a date, a decimal, a lock, a circular structure) used to raise a TypeError out of trace_tool and record a false tool error. It is now recorded with an <unserializable TypeName> marker in place of that value (the type name only), your tool’s result is returned unchanged, and a full queue or other recording failure is logged once. The TypeScript traceTool records an [unserializable result: Type] marker the same way, and in both SDKs a recording failure never replaces your tool’s own error or result
  • Python guardrail checks now run (behaviour change when you upgrade) — the Python SDK sent every guardrail check to a URL without the /v1 prefix, which the API answers with a 404. With the default fail_closed=False every check was skipped without a word and the call went through; with fail_closed=True every guarded call raised. Checks now go to /v1/guardrails/check, the same URL as the TypeScript SDK (a custom or self-hosted endpoint ending in /v1/ingest keeps its host). If you use guardrails= on a Python wrapper or call check_guardrails, your enabled rules now run on those calls: a block rule or a policy applied at deny raises GuardrailBlockedError, rules at warn or log and policies at dryrun or warn only record, each guarded call waits for the check (bounded by its timeout, 5 seconds by default), and the checks appear in the dashboard. A check that errors or times out still lets the call through unless fail_closed=True. Review your enabled rules, or set them to warn, before you upgrade — see Guardrails
  • confirmToolCall / confirm_tool_call: tell Zespan your app recorded a confirmation, and await_approval in Python — after your own confirmation step (a person pressed confirm in your UI), call zespan.confirmToolCall({ toolName, args, ref }) (TypeScript) or zespan.confirm_tool_call(tool_name, args=..., ref=...) (Python). It records a content-free approval span (the tool name and a hash of the arguments, never the arguments) and an entry the next tool-call check sends, so a require_confirmation rule can tell a confirmed call from an unconfirmed one. One confirmation unlocks one call, in one session, for the exact arguments you pass, and a confirmation recorded in another process is passed with withZespanContext({ confirmations }) / with_zespan_context(confirmations=[...]). Zespan checks that your app recorded it; it does not verify what the end user agreed to, and an app that records one without asking defeats the rule. With tool capture off, a confirmation from another process must be passed with its arguments. It never throws, and an older server ignores the two extra check fields. The Python SDK gets client.await_approval(...) with the same contract as the TypeScript awaitApproval (it blocks while it waits); an approved decision is also recorded for a rule that names human_review and reviewer in accept, and counts for no other rule. ZESPAN_CONFIRMATION_LEDGER=off stops the ledger — see Confirmations

Platform

  • Signup now asks you to confirm you are 18 or older — the required terms checkbox on sign-up and onboarding reads “I confirm I am 18 or older, and I agree to the Terms of Service and Privacy Policy”. Existing accounts are not asked again.
  • End-user erase and export now cover analysis results — erasing or exporting an end user in a project now also deletes or includes the findings and attribution results recorded for that user’s traces. An erase also removes the user’s rows from older copies of the trace tables where a deployment still keeps them; those copies are not in the export. See End-user data requests
  • Audit export files expire after 30 days — a completed audit export is deleted after 30 days (AUDIT_EXPORT_RETENTION_DAYS, 1 to 365, on self-hosted deployments) and its status becomes expired. See Audit log
  • Request logs no longer carry query strings or full client IP addresses — API request log lines keep the path only and a truncated client IP (IPv4 /24, IPv6 /48)
  • Inline images are also removed on the server for older SDKs — text from an older SDK version (or any client that sends text directly) is now checked at ingest with the same rule the current SDKs apply: a data:image/...;base64, URL, or base64 that starts like a PNG, JPEG, GIF or WebP file, becomes [IMAGE_OMITTED] in every text field before anything is stored. Other text and its structure are kept; a long base64 value that is not an image (a PDF or other file) is left as it is. Always on, no setting — see PII redaction
  • GET /v1/projects/{id}/functional-findings in the API reference — the findings list the dashboard uses is now documented: session authentication and the dashboard:read permission, the filters (check, confidence, agent, dimension, pack, layer, state, since, before, limit), the finding fields, who sees findings while they are on, in preview or off, the prevention field (locked below the Pro plan) and the errors
  • Capture readiness (off by default) — GET /v1/projects/{id}/functional/readiness?days=7 returns the share of checks that could run on the project’s recent traces, the inputs that cost the most with what to do about each, whether capture alone can fix it, and the figure per agent, as counts only: readiness is not a quality score. A check that cannot run now says why: not available from this integration, captured but empty, cut at the storage limit, could not be read, or does not apply to the trace. Self-hosted deployments can also store the output schema and read more from OpenTelemetry spans and older tool results — see Capture fields
  • Checks that could not run, and why (off by default) — when readiness is enabled for your deployment (self-hosted: CAPTURE_READINESS=on), a check that needs tool results reports “Could not run: tool results not captured” on a trace whose tool calls carry no result, instead of running on nothing, and GET /v1/projects/{id}/traces/{traceId}/functional/unchecked lists, per trace, the checks that could not run and the input each one lacked. It reads stored data only, shows the same visibility as findings, and answers 404 while readiness is not enabled
  • Python-literal tool results read as structure (off by default) — a tool result or argument stored as a Python str(dict) can be stored as JSON at ingest so result-reading checks see fields. Only dict and list literals are converted, nothing is evaluated, a literal that cannot be read (a date object, a cut value) stays text, and prompts and answers are never touched. Empty tool results are now kept as empty (not dropped), tool definitions over the size limit are slimmed (shorter descriptions, then names only, always every tool) instead of cut into invalid JSON, and a result cut at the storage limit is marked as cut
  • A drafted prompt fix needs its second approver on every path — a prompt version that Zespan drafted as a closed-loop fix can no longer be given a label (such as production) through the label route, the MCP zespan_prompt_set_label tool or ZespanPilot’s rollback: those answer with a pointer to the approval request, which a second person must decide. A fetch by name with no label and no version, or by the latest label, skips a drafted fix that is still waiting for approval; asking for its version number still returns it. Versions you author yourself behave as before.
  • Confirm-first in the dashboard: draft view, rule counts and an approval-queue switch (off by default) — a confirm-first draft in the Prevent section shows Waiting for confirmation data until your SDK has sent confirmations (TypeScript and Python code to copy, and a link to Confirmations), then Would have stopped N of M recorded calls when replayed (nothing was stopped) and how many of those followed a plain request from the user (judged on English messages only). It checks that your app recorded a confirmation, not that the end user agreed, and it can only warn (deny is locked). A policy with a require_confirmation rule shows a Confirm before destructive calls panel with the rule’s own checks over the last 7 days (confirmed, would have stopped by reason, and checks that could not run because the SDK sent no confirmation data), and a switch, off by default, that makes an approval-queue approval count as a confirmation: it needs permission to author policies, saves a new revision (nothing is applied) and is audited as policy.confirmation_approval_link.changed. New: GET /v1/projects/{id}/prevent/confirmation and PUT /v1/projects/{id}/prevent/confirmation/approval-link (404 while confirm-first is off, 403 below the Pro plan) — see Policies and the API reference
  • Prevention drafts say “Needs tool capture on” instead of “Not proven” — for a confirm-before-destructive-call draft, a project with tool capture turned off stores no tool context on its checks, so Zespan could not tell whether tool-call checks run and read it as Not proven: no checks seen. It now reads Needs tool capture on (turn tool capture on for the project, then recheck). Checks of the require_confirmation rule itself, which store only a fixed reason code, count as seen with capture on or off
  • Confirm before a destructive call, observe-only (off by default) — a new policy rule, require_confirmation, flags a call to a tool you declared destructive when your own app did not record a confirmation for that call: one confirmation unlocks one call, expires after 10 minutes by default, and can be tied to the exact arguments. Text in the conversation, a tool result or an argument never counts, and Zespan does not verify that the end user agreed. If your SDK sends no confirmation data, the rule reports no confirmation data and blocks nothing. The rule only records (warn at most): a policy with this rule cannot be set to deny, and it cannot be created from the guardrails page (422 with code: USE_POLICY_DOCS). POST /v1/projects/{id}/prevent/drafts can draft it for a finding where a tool you declared destructive ran with no confirmation step, tested by replaying the rule over your recorded approval spans; draft summaries gain confirmation counts. Without recorded confirmations the draft reads Not proven: no confirmation data. POST /v1/guardrails/check takes optional confirmations and ledger for the rule — see Policy file reference and the API reference
  • Tool, capture, mapping, retrieval and setting proposals, and more alerts (off by default) — POST /v1/projects/{id}/prevent/drafts now also saves a proposal for a change you make yourself, with Zespan’s own wording and the numbers recorded on your trace, never text from your traces (a tool name or property path appears only in quotation marks, escaped, at most 120 characters). Tool and MCP server contracts: structural defects in a captured tool definition, a tool whose definition changed between traces, a failed tool call whose result has no status field or that was repeated after an error, and a description and enum fix for tool arguments that broke the schema. Capture: which prompt or tool storage setting dropped the content, a missing system prompt, tool definitions or session id, no retriever spans, no MCP client spans, each with copy-paste TypeScript and Python. Mapping: the tool roles Zespan proposes, with the exact calls to send; nothing is saved for you, and a recheck reads your saved mapping and says Mapping saved. A fix that later traces can show reads Waiting for new traces and becomes Confirmed from later traces when a recheck sees it on newer traces; it can also read Not seen in later traces yet. In the dashboard, the Prevent section shows each proposal as a card (steps, numbers, copy-able TypeScript and Python code, and the alert state). Retrieval and provider settings (context length, timeout, rate limit, overloaded, server error) are advice with recorded numbers, and cut-off answers get a turned-off alert (Proven to alert) like the other measurable ones. A draft’s observedState and proposal.advice are new fields; the free plan still gets only the locked state and the kinds — see the API reference
  • Prompt and setting proposals, tested on your own model (off by default) — for a finding where a reviewed rule or a setting can help (a tool called with a different budget, audience, period or dates than the person asked for, a reply that claims or promises an action no tool performed, a required item missing from the answer, or an answer cut off at its output limit), POST /v1/projects/{id}/prevent/drafts now saves a proposal: a short block of reviewed rules to add at the end of your prompt, or one recommended value for the maximum output tokens. Zespan never edits or deploys anything: copy the block, or take the body that saves it as a new prompt version with no label (name a managed prompt with promptName), and ship it yourself. A prompt in a language other than English stays advice only. POST .../drafts/{draftId}/rerun can then test it, only when you ask: it first returns a cost estimate from token counts and your own model price (refused if the price is not known), and runs only after you confirm that estimate, on your own model connection and bill, with a spend limit per rerun and per organization per day, only when AI processing is on for your organization, and only on traces whose prompt and message were captured and that are a single step. It reruns the original trace without and with the change and a sample of healthy traces with it, judged by the same check that raised the finding. The result reads Rerun result on N traces (Helped in reruns, or not proven with the reason) and is never called proven. Some findings are advice only, because a one-step rerun cannot judge them. ZespanPilot gets prevent.rerun_estimate (read) and prevent.rerun (confirmation first, shows the cost); it never applies a proposal — see the API reference
  • Findings card on a trace and a Findings page (off by default) — a trace with findings shows Findings on this trace under the verdict, and Findings in the sidebar lists the project’s findings, each with its title, tier (Proven only when recomputed from the stored spans, otherwise Likely or Signal), cause, fix, evidence, where it broke and the Prevent section; free plans see the written advice and the locked line, never draft content. Filter by agent, state, check family, tier and time range. It shows only what the API allows: nothing while detectors are off, owners and admins only (marked Preview) while they run in preview. GET /v1/projects/{id}/functional-findings gains agent and since filters. Self-hosted: build the dashboard with NEXT_PUBLIC_FINDINGS_UI=1 (Prevent inside it still needs NEXT_PUBLIC_PREVENT_UI=1) — see Findings
  • Safer regular expressions in policy schemas and local rules — a tool or output schema in a policy that contains a regular expression that could take unbounded time (for example ^(a+)+$) is refused when you create or edit the policy, and a rule saved earlier with one is skipped at check time and recorded as an invalid pattern instead of running (a fail-open policy lets the call through, a fail-closed one blocks it). Patterns in a schema are tested against at most the first 10,000 characters of a value. The TypeScript SDK applies the same checks to regex and secret_egress rules it evaluates locally, and sends the check to the server when a pattern fails them — see Policies
  • More prevention drafts: five more policies, turned-off alerts, and one-click regression cases (off by default) — drafts now also cover a calls-per-tool cap for a run that looped to its turn limit, a create-once guard for duplicate creates, forbidden phrases taken from your own instructions, an answer length limit in characters, and a tool-argument schema taken from the tool’s own definition; each is tested on your recorded traffic like the first three, and a phrase, limit or schema that is not the same across most of your traces is never used. For an anomaly or window finding that an alert metric measures (error rate, p95 latency, daily spend, tool errors, empty answers), a draft is an alert rule saved turned off with no notification channel, shown as Proven to alert (it would have fired when this happened, and rarely otherwise over the last 30 days) and never as prevention; you turn it on from the alerts page, and from then on it notifies at most once every 7 days (a breach inside the 7 days is recorded in the alert history, not notified). POST /v1/projects/{id}/prevent/regression-case adds a finding’s trace to one of your datasets on any plan. issueRef accepts a time window for alerts, and draft summaries gain alertRuleId — see the API reference
  • Prevention drafts, tested on your own traffic (off by default) — for a finding Zespan caught, POST /v1/projects/{id}/prevent/drafts builds a policy from that agent’s recorded traffic (a tool allow-list from the agent’s declared tools, a repeat guard from its clean traces, or a JSON-output rule) and replays recorded tool calls and answers against it. It is shown as proven only if it would have stopped the original trace and flagged almost none of the normal ones over enough recent traffic; otherwise it is saved as not proven with the reason and no policy is offered. A proven draft is a policy at dryrun that nobody applies for you: apply it through the existing policy apply, which keeps its own checks. The written advice on a finding stays free; drafting needs the Pro plan and the prevent:draft permission (owners, admins and editors), reading drafts needs prevent:read, and below Pro a finding carries only prevention: { locked: true, kinds } — see the API reference
  • Prevent section and policy provenance in the dashboard, and in ZespanPilot (off by default) — an issue with a cause proven from its spans shows the written advice (free) and, when a draft template exists, Create draft, Proven on your traffic with the numbers (“Caught 12 of 12 similar traces, 0 false hits, over 14 days”), Not proven with the plain reason, or a locked line below the Pro plan with no draft content. Review and Apply… go to the existing policy review and apply screen, and applying starts at dryrun. A policy that came from a draft shows Where this came from (finding, test numbers, last checked, Recheck), and a policy at deny whose latest test could not evaluate every rule shows Unproven with those rules. ZespanPilot gets prevent.list and prevent.get (reads), and prevent.draft and prevent.recheck (confirmation first, Pro plan, permission to draft); it never applies or promotes a draft. Self-hosted: build the dashboard with NEXT_PUBLIC_PREVENT_UI=1. GET /v1/projects/{id}/policy-docs rows gain unprovenRules, prevention draft summaries gain days and policyDocId, and GET /v1/prevent/catalogue entries gain draftable — see Policies and Issues
  • Safer policy testing and deny — setting a policy to deny now needs a saved test of the exact current text that could evaluate every rule. Stored traffic does not carry tool calls or live counters, so tool, loop, spend, rate and region rules are shown as Not evaluated, the test is not saved as proof, and deny is refused for that policy (enforce it at warn, or accept the risk with --untested / Apply without a test). --force no longer skips this check. Running a policy test now needs permission to author policies (owners, admins and editors), not just read them, and it needs a plan with policies. A policy at dryrun or warn now stays at that level for every pattern inside a regex rule, so a pattern’s own block or redact can no longer change traffic. Editing a guardrail that a policy manages (its action, settings, type, phase, agents or priority) from the guardrails page or PATCH /v1/projects/{id}/guardrails/{guardrailId} is refused with 409 and code: POLICY_OWNED: edit the policy and apply it instead; turning the rule off or renaming it still works. The assistant’s policy promotion now creates a revision and applies it through the same checks as the dashboard — see Testing policies and Policies
  • Changes on your charts, typed changes on the Changes page, and change keys in Settings — the Overview, Performance (latency over time) and Errors charts mark changes (deploys you reported from CI, prompt deploys, agent promotions, config and policy edits) as dashed lines, with a Changes button to hide them. The Changes page gains Service, Environment and Version columns, a Type filter, copy-ready steps for GitHub Actions, GitLab CI, curl and your own app, and Report a change takes the same optional fields. Settings → API keys → Change keys creates and revokes the write-only keys; reported changes are kept 400 days — see Changes, API keys and Report changes from your CI
  • Reported changes can name the right change in findings (off by default) — a change reported from CI can be cited by a finding, is merged with the new service version first seen in your traces when both describe the same deploy, and reaches behaviour drift checks in plain wording (“a deploy reported by your CI”). It never raises a finding on its own above “likely” and is never treated as proof of the failing layer. The findings you see do not change until this is turned on for your project (self-hosted: CHANGE_CI_ATTRIBUTION=on) — see Report changes from your CI
  • Report changes from your CI — POST /v1/changes takes a typed change (kind of deploy, prompt, model, config, flag, dependency or other, plus service, environment, a short ref, version and attributes) with metadata only: no commit message, author, file name or diff, and an email address or a denied attribute name is refused with 422. A write-only change key (zsc_..., Settings → API keys → Change keys) can call only the change routes. Reporting the same release twice stores one change, and each project accepts 60 reports a minute and 2,000 a day. POST /v1/projects/{id}/changes accepts the same typed fields, and a change posted from the dashboard keeps your user ID, never your name or email — see Changes and Reporting changes from CI
  • A call seen by both your SDK and an AI gateway can have its tokens and cost counted once (off by default) — when merging is on and your app uses the Zespan SDK while a gateway that also exports to Zespan sees the same call, both rows are kept, the gateway’s span nests under the SDK span when they matched by trace link, and tokens and cost are counted on one row, never on a row without tokens (call counts built from hourly totals still count both rows). The gateway’s row can be held back up to 90 seconds in projects that also send SDK events. Until merging is on, and whenever Zespan is unsure, both rows are counted. Self-hosted: GATEWAY_DEDUPE=on — see Using the SDK too
  • Gateway and OTLP data card — Settings → Instrumentation → Gateway and OTLP data shows, per source, when data last arrived, JSON or Protobuf, and what share of model calls came with tokens, a span kind and text, with a one-line fix per problem; GET /v1/projects/{id}/otlp-status returns the same — see Instrumentation
  • Higher OTLP limits — /v1/traces accepts bodies up to 8 MB after decompression (was 1 MB) and 3000 requests per minute per project (was 300 per key); large exports are split into smaller jobs. /v1/ingest keeps its limits — see OTel integration
  • Gateway spans in failure attribution — a failed attempt of an AI gateway (a rate limit, a rejected key, a server error, a timeout) is now evidence for the infrastructure and model layers of a provider-error finding, never for tool or retrieval; the origin stays at Possibly — see Stack layers. Gateway spans have their own gateway kind in the traces filter
  • New gateway guides — setup pages for Portkey, Vercel AI Gateway, Bifrost and Envoy AI Gateway, OpenRouter Broadcast on the OpenRouter page, and a verify step and a “Using the SDK too” section on every gateway page
  • A gateway’s cache-hit flag no longer makes a call free — calls are priced from Zespan’s table and your custom prices; a gateway’s own figure stays evidence
  • AI gateway traces land correctly — spans a gateway exports to /v1/traces (LiteLLM, OpenRouter, Portkey, Kong, Vercel AI Gateway, Cloudflare AI Gateway, Bifrost) with the older GenAI attribute names (gen_ai.usage.prompt_tokens), OpenInference names (llm.model_name, llm.token_count.*) or the gateway’s own names are stored with their model, provider, token counts, cache and reasoning tokens, user, session, model-call kind and, where your capture settings allow, prompt and answer text; before, they landed with zero tokens and no kind. Spans stored earlier are not rewritten, so totals for those sources rise from this release. This applies to any app that sends these names, not only gateways (for example OpenLLMetry’s older token names and OpenInference instrumentation), and enduser.id and gen_ai.conversation.id now fill the user and session: LLM call counts, session grouping and token or cost alert rules can change for new traces. A gateway’s proxy, routing, infrastructure and retry or fallback attempt spans are stored as the new gateway span kind and are no longer counted as model calls; failed attempts keep their HTTP status, error type and provider. Calls are priced from Zespan’s table (and your custom prices); the gateway’s own cost figure is kept as evidence and used only for a model Zespan has no price for when GATEWAY_COST_FALLBACK=on. Self-hosted: OTEL_GATEWAY_MAPPING=off turns the mapping and the gateway handling off together — see OTel integration, span kinds and environment variables
  • Gateway identity metadata is dropped at ingest — gateways attach the end user’s e-mail, the requester’s IP address, key hashes and ids, team and key aliases and request headers to their spans (LiteLLM does it for every request). Zespan now removes these before storing, with content capture on or off; the end-user id you set yourself stays, and prompt and completion text still follow your capture and redaction settings. Self-hosted: GATEWAY_META_DENYLIST=off keeps them — see Security
  • Fix: OTLP protobuf exports are no longer dropped — spans sent to /v1/traces as application/x-protobuf (the default for LiteLLM’s and Kong’s exporters, the OpenTelemetry Collector’s otlphttp exporter and the Python opentelemetry-exporter-otlp-proto-http package) were dropped, or stored with corrupted trace and span ids, while the endpoint answered 202. They are now stored with the trace and span ids they were sent with; spans sent before this release cannot be recovered. The endpoint also accepts Authorization: Bearer <project key> as well as x-api-key, OTLP/JSON with enum names ("kind": "server", "STATUS_CODE_ERROR") and base64 ids, and answers 200 (was 202) with partialSuccess.rejectedSpans counting spans it could not store (unusable ids, no start time, over 512 per request). An unparseable body is 400. A request body over the size limit (8 MB after decompression) is 413 with an explanation, which exporters do not retry: lower the batch size. — see OTel integration
  • Fix: latency, cost and token alert rules created in the dashboard now check the metric you chose — the scheduled check compared the number of requests in the window to the threshold for latency and cost rules (a “Latency > 2000” rule fired once the window held more than 2000 calls), and evaluator-score rules were compared the same way. Rules now read average latency, spend and token count, and evaluator-score rules are left to the live evaluation (POST /v1/projects/{id}/alerts/evaluate) — see Alerts
  • Saved alert and webhook credentials are hidden — webhook URLs and secrets, Slack and Discord URLs, PagerDuty and OpsGenie keys and Jira and Freshservice tokens, on alert rules and prompt webhooks, are shown as Saved (hidden) to every role (viewers could read them from the alerts API before). Saving a rule without editing a channel keeps its credential; changing a channel’s type or destination needs the credential entered again — see Alerts
  • Python outcome() and a typed OutcomeError — zespan.outcome(...) reports a business outcome from Python with the same request as the TypeScript SDK; both SDKs raise OutcomeError with a code (missing_trace_id, http_error, network, timeout) — see Outcomes
  • Changes from the dashboard no longer record your name — a change posted from a dashboard session without a source is stored as dashboard, and deleting your account removes your name or email from change sources you posted earlier — see Changes
  • Outcomes respect session erasure — an outcome reported with a sessionId you erased in the last 30 days is dropped, like events; the response reports dropped. Outcomes carry no user id, so erasing a user by userId does not drop later outcomes, and outcomes that carry only a trace id are not matched — see End-user data
  • Self-hosted: a malformed credential encryption key is reported at startup — if LLM_CONNECTION_SECRET is missing or not 64 hex characters in production, the API and worker log an error naming the variable, and /health called from inside the API container reports credentialKey as ok, missing or invalid (the value is never shown; public callers don’t see the field)
  • Masked phone numbers and dates match across formats — phone numbers and dates of birth are masked in a canonical form, so +1 415 555 0134 and (415) 555-0134, or 1984-03-12 and 12/03/1984, get the same placeholder within a trace; placeholders for these two kinds change for data stored from now on. Zespan also keeps the kind, checksum validity and, for cards and IBANs, the last 4 characters next to placeholders — see PII redaction
  • OTel tool spans keep their tool’s name — spans from OpenTelemetry GenAI, Google ADK, Vercel AI SDK, OpenInference, OpenLLMetry and Laminar now carry the tool name, arguments, result and error status; ai.toolCall spans are tool calls; hand-offs named handoff to <agent> are hand-offs. A new project setting, otelToolSpanKind (on for new projects, off for existing ones), controls whether OpenInference, OpenLLMetry and Laminar tool spans are counted as tool calls — see OTel integration
  • Existing projects can be switched to count OTel tool spans — for projects created before the otelToolSpanKind setting (off), Zespan can turn it on after a dry run that shows the change in tool-call counts. From then on, OpenInference, OpenLLMetry and Laminar tool spans count as tool calls, so tool-call counts and tool error rates change for new traces (a failing tool that was invisible now shows as a tool error); data already stored is not rewritten, and the change can be reversed per project
  • Project settings accept a per-pack tool mapping — PATCH /v1/projects/{id} takes functionalMappings (pack id to the mapping that tells a pack which of your tools does what, up to 64 KB), next to functionalPacks; the audit trail keeps counts only
  • Confirm tool roles once — GET and PATCH /v1/projects/{id}/functional-mappings/proposals propose what each tool does from its name, description and parameters, and store your confirmation with the tool’s schema; a changed tool returns to proposed and a role that could move money or delete is never proposed automatically
  • Where it started, on issues, incidents, traces and the overview — a failure now shows where it showed (Failed at: Tool step) and, separately, which stack layer it most likely started in (Instructions, Agent logic, Tool, MCP connection, Retrieval, Memory, Model, Guardrail, Source data, Infra / platform), worded by how sure Zespan is: Caused by, Likely, Possibly (muted, with its evidence) or Either … or … with the step that would settle it. Weaker claims are shown only for layers and setups where Zespan has validated them; otherwise you see the Failed at line and what data is missing. Issues get a Layer filter over analysed issues, traces get a Where it broke row, and the Overview and each agent get Failures by layer — see Stack layers
  • Instrumentation page and who gets each layer — Settings → Instrumentation shows per layer how much of your traffic was seen through real spans and the one step that would confirm guesses. Owners and admins can set, per layer, an owner and an existing alert rule whose channels are notified; incidents with a shown origin are assigned by layer, then to the agent’s owner, then to the organization owner. PATCH /v1/projects/{id} takes stackLayerOwners, and STACK_LAYER_ROUTING=off turns it off for self-hosted deployments — see Instrumentation
  • Confirm or correct the layer — under a shown origin, Was it Retrieval? Yes / No, it was… records your answer (item, claimed layer, your answer, your role and the time; no name, no free text). GET and POST /v1/projects/{id}/stack-labels read and write it, and GET /v1/projects/{id}/stack-health returns the counts behind the Overview card — see Instrumentation
  • New alert metric mcp_error_rate — the fraction (0 to 1) of MCP client spans in the window that errored — see Alerts
  • Layer on list rows and incident cards — each analysed row in the Issues list carries a small tag with the layer its cause started in, and each incident card shows the same layer chip under its cause, with the same wording and the same rules for when it is shown, whenever the chip names a layer — see Issues and Incidents
  • Layer answers are kept for two years — each Was it …? answer is deleted 730 days after it was last given — see Confirm or correct the layer

SDK

  • Tool definitions and tool calls on every model call, and tool-only replies are checked (TypeScript and Python) — every LLM wrapper now records what the model was offered (tool names, argument names, descriptions) and each tool call it asked for, in order and with repeats, on the span. Both follow your tool I/O capture setting and redaction: with it off, nothing new is sent. A reply that is only tool calls used to skip the guardrail check; it now gets one check per call, with the earlier calls of the trace (a call is no longer counted among its own earlier calls). Tool misuse and loop detection rules can now be set to run post or both. The Python SDK now sends the same tool context.
  • gatewayCorrelation / gateway_correlation and provider response ids (TypeScript and Python) — an opt-in option, off by default, that adds traceparent (and Cloudflare’s trace headers) to provider requests for the gateway hosts you list, so a gateway that honours them nests its span under the SDK span and the call’s tokens and cost can be counted once; every LLM event also records the provider’s response id as gen_ai.response.id — see Using the SDK too
  • Agent steps, handoffs and guardrail spans — LangGraph node runs are recorded as agent_step spans; Google ADK agent spans carry the model-call count, the sub-agents they could hand to and a handoff stop reason, and the Python ADK wrapper now records a handoff span like the TypeScript one; manual delegateTo / delegate_to, the AutoGen and CrewAI handlers and the zespan-crewai and zespan-autogen middleware record step numbers and stop reasons; every guardrail check is recorded as a content-free guardrail span under the span it checked (action and the rules that fired, no text) — see Agent steps and Guardrail spans
  • Tool calls join the tool’s trace in LangChain and Google ADK — a model, MCP or HTTP call made inside a tool used to start a trace of its own unless you wrapped the run yourself, so one agent run showed up as many traces and trace counts were inflated. The tool’s span is now the current context for the length of the tool call, in both languages — see LangChain
  • Google ADK for TypeScript records a tool that throws as an error — ADK for TypeScript reports a failed tool only to plugins, so with the agent callbacks alone it was recorded as a successful tool span. Register the new plugin next to the callbacks: plugins: [handler.plugin] (Python is unchanged and was already correct) — see Google ADK
  • A LangChain guardrail block stops the call (TypeScript) — with guardrails on, the TypeScript handler logged a block and the model call went on. It now throws GuardrailBlockedError, as the Python handler already did; any other problem inside the handler stays inside it. With LANGCHAIN_CALLBACKS_BACKGROUND=true a block can only be recorded, and the SDK warns once. To keep the old behaviour of recording without blocking, drop guardrails from the handler, or set the rule’s action to warn in the dashboard. A check that cannot run stops the call only with failClosed: true, as in Python. Also fixed: after a blocked call, auto-instrumented model calls elsewhere in the app were no longer recorded — see LangChain
  • No initialize span when an MCP client reconnects (TypeScript) — a client that reconnects with a transport that already has a session id skips the handshake, so the SDK no longer records one — see MCP
  • Switch for guardrail spans — guardrailSpans: false / guardrail_spans=False, or ZESPAN_GUARDRAIL_SPANS=off, stops the SDK from recording each guardrail check as a span; each span counts toward your event quota — see Guardrail spans
  • spanKind on zespan.createSpan / withSpan (TypeScript) — the client methods now accept the same spanKind as the standalone functions — see OTel integration
  • OpenTelemetry stack span kinds (off by default) — with OTEL_MAP_STACK_SPAN_KINDS=on, OpenInference retriever, reranker, embedding, agent, chain and guardrail spans, and the GenAI retrieval and invoke_workflow operations, are stored under those kinds; createSpan / withSpan (spanKind) and create_span / with_span (span_kind) add the attributes for you — see OTel integration
  • Fix: ESM apps no longer crash in initZespan (TypeScript SDK 1.3.6 to 1.6.0; upgrade) — in @zespan/sdk 1.3.6, 1.3.7, 1.5.0 and 1.5.1, an app that loads the ESM build (import in Node, or a bundler that picks the import entry) got Dynamic require of "@opentelemetry/sdk-trace-node" is not supported from initZespan with the default enableOTel, and withSpan then failed too. Apps bundled into one file without node_modules could also fail to load the module. The tracer provider is now imported statically, so every build and bundler includes it; if it still cannot start, the SDK logs one warning, keeps sending Zespan events and withSpan still runs your code. CommonJS apps, Bun and the Python SDK were not affected.
  • MCP calls are traced automatically — calls through the official MCP client packages (@modelcontextprotocol/sdk and @modelcontextprotocol/client in TypeScript, mcp 1.x and 2.x in Python 3.10+) become mcp_client spans with the server, transport, whether the tool was listed, whether your arguments fit its schema, the error code and tool-error flag; metadata only, on by default, never throws into your code, ZESPAN_MCP_AUTOPATCH=off turns it off. mcpServerTool() / @mcp_server_tool wrap the tools of your own MCP server and join the caller’s trace. Tool spans from MCP adapters in ADK, LangChain and the Vercel AI SDK carry the tool and server name. Each tool call is also stored as an MCP tool call — see MCP
  • OpenAI Agents SDK integration — instrumentOpenAIAgents() (TypeScript) and instrument_openai_agents() (Python) add a tracing processor: function spans become tool calls with name, arguments, result and error, hand-offs become handoff, guardrails become guardrail, generations become llm; only names and status are sent when the Agents SDK’s sensitive-data switch or tool IO storage is off — see OpenAI Agents SDK
  • Claude Agent SDK integration — claudeAgentHooks() (TypeScript) and claude_agent_hooks() (Python) add hooks to the library’s options: one tool span per tool use with name, arguments, result and error; a tool use that never finishes is closed as an error when the session ends. Observation only, nothing in your agent changes — see Claude Agent SDK
  • Fix: LlamaIndex and Semantic Kernel tool spans — LlamaIndex tool calls are one span with the tool’s name, arguments and result (Python read the arguments as the name; TypeScript sent two spans and recorded the result as [object Object]), and a call that fails is closed as an error; Semantic Kernel tool functions now carry their name, arguments and result — see LlamaIndex and Semantic Kernel
  • Vercel AI tool calls keep their arguments and result — the Vercel AI exporter records ai.toolCall.args and ai.toolCall.result on tool spans (the name and status only when tool input and output storage is off) — see Vercel AI SDK
  • Fix: CrewAI and AutoGen middleware events now reach Zespan, with tool calls — zespan-crewai and zespan-autogen (1.2.2) sent their batches in a format /v1/ingest rejects with 400 No valid events found in batch, so no span from them was stored. They now send NDJSON in the SDK event shape, and tool usage becomes a tool span with its name, arguments, result and error status. Upgrade both packages to see traces
  • Tag what a tool does and record approvals — traceTool(..., { role }) / trace_tool(..., role=...) and recordApproval / record_approval (TypeScript and Python); awaitApproval now records its approval span — see Agent tracing
  • Google ADK tracing handles BaseLlm models and span metadata — agents built on an ADK BaseLlm instance now record its model id instead of an object, per-call spans record the model that answered (modelVersion) after a failover, and wrapADKAgent and wrapADKRunner accept a metadata option merged into span metadata — see Google ADK

AI features

  • ZespanPilot words layer claims the way the attribution allows — “caused by” only for Caused by, “likely” for Likely or better, “possibly” for Possibly, “either … or …” only for the two layers of an ambiguous attribution; a claim the attribution does not support is listed as not checked — see ZespanPilot
  • ZespanPilot inserts figures straight from query results — the model now refers to each figure by an id from the query result and Zespan writes the exact value; differences, ratios, percentage changes, shares, rankings and counts are computed by Zespan. Each figure opens to show the query, row, column, exact value and window it came from, each figure is checked against the model, metric, time window and direction its sentence names (a mismatch is marked ⚠ conflict), and the footer says whether figures were inserted from query results or typed by the assistant; reopened conversations keep it — see ZespanPilot → Where the figures in an answer come from
  • Newer Claude models without a temperature setting work on your own connections — for your own Claude models that don’t accept a temperature setting, Zespan now leaves temperature unset automatically — see LLM connections

Playground and simulations

  • Playground tool calls and streaming work on every provider — assistant tool_calls, tool messages with tool_call_id and name, nullable message content, and the top-level toolResults shortcut are translated for OpenAI, OpenRouter, Anthropic, and Google. Anthropic and Google runs now stream as chunked text/plain; multi-target and Bedrock or Vertex runs return JSON with streamed: false. System prompt, temperature, top_p, stop, and seed map to each provider’s own fields, and OpenAI reasoning models get max_completion_tokens instead of max_tokens. Inputs are bounded at 64 tool_calls per message, 100 toolResults, and 100,000 characters per argument or result — see Playground → Running the Playground from the API
  • Starting a simulation batch returns more — the 202 response now includes batchRunId, runCount, status, and a batchRun object. The live run stream (Server-Sent Events) now sends CORS headers to allow-listed origins — see Simulations

Traces, sessions, and tools

  • See how each call ended — traces and spans show Truncated, Blocked, Refusal, Empty response, and Stream cut chips (each explains itself and links to the fix), the traces list gains an outcome filter and outcome_kinds on every row, and OpenTelemetry spans fill the finish reason from gen_ai.response.finish_reasons when it is missing — see Traces and the finish outcome reference
  • Silent-failure coverage — a reference page lists the silent failures Zespan looks for, the check families you can filter findings by, and what each check needs from your traces — see Silent-failure coverage
  • Alerts for silent failures — new alert metrics truncated_rate, blocked_rate and empty_response_rate (0 to 1 fractions, at least 20 calls in the window) — see Alerts
  • Causes for cut-off, blocked, empty and unfinished answers — incidents and issues can name a rise in truncated answers, blocked or refused answers, streams cut short, or empty responses, with example calls — see Traces
  • Trace search and session filters work — search on the traces list, and user_id, env, and search on the sessions list, now filter results as documented — see Sessions
  • Tools list and tool detail agree — a call is counted once, and unique_tools counts tools by name — see Tools

Evaluations and alerts

  • Viewers can read evaluation runs, and OpenRouter is accepted as an LLM-judge provider on templates and evaluators (it needs an OpenRouter LLM connection) — see Evaluations
  • Alerts — editing a rule so it no longer has an evaluator turns it back into a threshold rule. New metrics tool_error_rate and guardrail_block_rate, both 0 to 1 fractions — see Alerts

Errors, performance, and costs

  • One error rule everywhere — Errors, error-rate alerts, and performance rates classify errors by error_code first and then status. Error groups can have error_code: "unknown", and the timeline has an other series — see Errors
  • Performance API returns summary { p50, p90, p99, count } and slaCompliance.target_set — see Performance
  • Users API request_count counts LLM calls, and a new span_count counts all spans — see Costs
  • Cost attribution and forecast APIs — cost attribution gains meta, environment, timezone, range=today, and compare=true. The forecast gains projected_total_30d, per-point low and high, residual_std, environment, insufficient_reason, and history_days, and daysUntilQuota is now correct and null when the quota isn’t reached by month end — see Costs

One window rule and consistent counts

  • Every range is the same window on every page — 24h, 7d, 30d, and 90d are trailing 24, 168, 720, and 2,160 hours ending now (7d is no longer 7 calendar days), and today is your calendar day in the time zone you pass. Overview, Costs, cost attribution, Models, Users, Agents, Tools, Errors, and Performance all use it — see Overview → How time ranges are measured
  • Overview API adds kpis.span_count, kpis.llm_call_count, kpis.error_rate_delta_pt (percentage points), and kpis.error_rate_delta_rel_pct, and returns timeline_bucket (hour for today and 24h, otherwise day) with hourly timeline points — see Overview API
  • Costs API returns a totals object with cost, span_count, llm_call_count, and error_span_count, and cost attribution rows add span_count and llm_call_count — see Costs API
  • Model detail share_of_calls is now measured against LLM calls only, and the response adds project_llm_calls and project_span_count
  • Errors — tool errors now include any tool_* code and validation_error, rate_limit_exceeded counts as a rate limit, toolErrors[] lists every calling_agents, and most_affected_model ignores spans with no model — see Errors
  • Performance — tokens_per_sec is output tokens per second of LLM latency (it used to include prompt tokens), and a slow trace’s duration_ms is wall-clock time to the end of its last span — see Performance
  • Agents and Tools — the agent list and detail now include LLM spans (attributed to the trace’s dominant agent) and count distinct spans, and tool names are bare everywhere, with toolName accepting search_kb or tool.search_kb — see Agents and Tools
  • Value — each outcome is credited to one agent and one model by trace, using the trace’s most frequent named agent and model — see Value

Changes, annotation queues, policies, datasets, and Playground errors

  • Changes — POST /v1/projects/:id/changes takes optional source and actorLabel, returns the stored source, and the feed’s actor has a displayName that is never a raw id — see Changes
  • Annotation queues can be renamed and deleted — PATCH and DELETE /v1/projects/:id/annotation-queues/:queueId — see Annotation queues
  • Policies — DELETE /v1/projects/:id/policy-docs/:docId archives a policy (a soft delete, same checks as archive) — see Policies
  • Datasets — the from-traces preview total counts unique traces, and spans counts matching spans — see Datasets
  • Playground provider errors return 422 provider_auth_failed, 429 provider_rate_limited, 502 provider_unavailable, or 422 provider_request_rejected instead of relaying the provider’s status — see Playground

Account, settings, and organization

  • Account security page — change your password, set up two-factor authentication with downloadable backup codes, see every browser and CLI terminal signed in to your account, sign out one or all others, and revoke a terminal. New routes GET /v1/user/sessions, DELETE /v1/user/sessions/{id}, POST /v1/user/sessions/revoke-others, GET /v1/user/cli-devices, and DELETE /v1/user/cli-devices/{id}, recorded as user.session_revoked and user.cli_device_revoked — see Account security
  • MCP keys default to 90 days — the expiry menu starts at 90 days (30 days, 180 days, 1 year, or no expiry are available) — see API keys
  • Rotating a project key asks for confirmation, shows “Previous key still valid until …”, and ends an older previous key immediately if you rotate twice within 24 hours — see API keys
  • Your only production environment is protected in the dashboard — its Production switch is locked and deleting it needs its name typed — see Environments
  • Seats count invitations, and invitations explain themselves — the Members page counts pending invitations toward seat usage, inviting past your plan’s limit returns 403 SEAT_LIMIT_REACHED with a message, and the invitation page shows the organization, role, and expiry, or why a link no longer works — see Organizations
  • Subscription status on the billing page: Active, Payment past due, Canceling (with Resubscribe), or Free tier — see Billing
  • Privacy exports have an API — GET /v1/gdpr/dsar-requests reports hasDownload, and GET /v1/gdpr/dsar-requests/{id}/download returns a 5-minute link — see Privacy
  • Self-hosting: new BETTER_AUTH_TRUSTED_ORIGINS setting adds extra dashboard origins for sign-in. In production, http: origins are ignored unless they are localhost or 127.0.0.1 — see Environment variables

Audit and compliance

  • New audit events — alert.rule_enabled, alert.rule_disabled, annotation_queue.created, annotation_queue.updated, and annotation_queue.deleted. Project-scoped actions are now recorded against your active organization so they show in the organization audit log — see Audit log
  • Audit exports accept a risk filter and return downloadUrl and storage. fileUrl is null for locally stored files. Three download routes serve completed exports, and an incomplete export returns 409 — see Audit log → Export API
  • Evidence packs show who generated them (generatedByName, generatedByEmail). Packs stuck for more than 30 minutes time out to failed, and failed packs can be retried or deleted. The verify response adds status, packStatus, and reason — see Evidence packs and Verification

Dashboard redesign

  • One cost section — Costs, Attribution, Value, and Forecasts share a single Explorer · Attribution · Value · Forecasts tab strip, and Explorer and Attribution share one Compare to previous toggle — see Costs → The cost section
  • Consistent range controls — analytics and list pages offer 24h, 7d, 30d, 90d and open on 7d; live views (Overview) offer 1h, 24h, 7d, 30d and open on 24h. Today is gone from the controls on Overview, Sessions, and organization Analytics, and an old range=today link reads as 24 hours — see Overview → Range controls on other pages
  • Range controls, by page — Evaluations offers 1h, 24h, 7d, 30d and Custom; Agents and Models 7d, 30d, 90d; Issues 24h, 7d, 30d, 90d; Sessions, Guardrails, and organization Analytics 24h, 7d, 30d. Today is dropped from Guardrails too. Guardrails now has a range control saved in the URL (?range=), and its key figures are a standard strip with the verdict band above it. On trace detail and Issues the verdict also sits above the key figures, and the Costs, Attribution, Value, and Forecasts loading state shows the tab strip — see Overview → Range controls on other pages
  • Overview verdict band — shows only when something needs attention (System degraded or Some data unavailable), with a stacked title and key sentence and one primary action; a healthy project opens on its key metrics. The ZespanPilot digest opens from the band’s sparkle button — see Overview
  • Guardrail tests explain themselves — each result says what the rule checked, flags PII in the sample that the rule doesn’t cover (with an Enable shortcut), and says whether it ran your draft or the saved rule. POST /projects/{id}/guardrails/test adds checked, notCovered, and source (draft or saved) — see Guardrails → Testing a guardrail
  • Trace detail has one Evaluations row — only lenses that have scored show a chip, and an unscored trace shows one line — see Traces
  • Issues — Generate fix, Resolve, and Dismiss appear on hover, on the selected row, and on keyboard focus, and the table has a Remediation column — see Issues
  • One name: Simulations — the sidebar and every page now say Simulations, and AI Hub tabs and buttons use sentence case (Convert to scenario, Playground Run all and Stop all, Tools and structured output) — see Simulations and Playground
  • One error panel — every page that fails to load shows “We couldn’t load …” with a plain-language message and Retry, never a raw “Internal error” — see Overview
  • Numbers that agree — Costs Daily average is the mean of the newest 7 daily buckets (same as Forecasts) (Costs); cost per success is total cost ÷ successful calls (Models); the Traces summary reads “In the 30 loaded: p50 … p95 … cost” with a range-wide failure % (Traces); Audit risk and failure counts come from the whole log (Audit); Sessions and annotation tiles say “the loaded N” (Sessions, Annotation queues); the model lifecycle countdown is computed from the retirement date (Models)
  • Agents — the error-rate sub-line reads “N of M spans”, a health score shows only when it agrees with the agent’s status, and the attention card is a verdict band — see Agents
  • Agents show Health N only when an agent has a real score — see Agents
  • Quieter empty states — a range with no data says so and offers a wider one, and KPI strips whose tiles are all zero are hidden
  • Policy activity names the person who ran each apply, instead of a user id — see Policies
  • Sidebar — pin and unpin items (hover and click the bookmark, or press P), and Compliance is under Monitor. Settings has its own grouped navigation — see Overview → Finding your way around
  • Sign-up asks for the Terms first — the form stays disabled until you accept the Terms of Service and Privacy Policy. The two optional choices, product analytics and product emails, start off, and advertising measurement is no longer offered at sign-up — see Onboarding and Privacy
  • AI processing switch hidden — the organization AI processing setting isn’t shown in the dashboard for now. The setting and its behaviour are unchanged — see Organizations → AI processing

Platform updates — privacy controls, AI processing setting, and more reliable delivery

Released: October 2026

Models

  • Gemini 3.x models — In LLM Connections model pickers, the current Gemini API models are listed first (gemini-3.8-flash, gemini-3.6-flash, gemini-3.5-flash-lite, gemini-3.1-flash-lite, gemini-3.1-pro-preview, gemini-3-flash-preview) and Gemini 2.5 models are marked (legacy) but stay selectable. Zespan now prices these models, including the gemini-3.8-flash / gemini-3.6-flash price change on January 1, 2027 and the gemini-3.1-pro-preview rate for prompts over 200k tokens. When an ADK or google-generativeai integration can’t read the model from a call, both SDKs now record gemini-3.8-flash — see Supported models

Security

  • Outbound requests only go to public addresses — LLM connection URLs, simulation and dataset HTTP targets, webhooks (alert, prompt, Jira, Freshservice), and SSO discovery are refused when they resolve to a private, internal, or metadata address. Hostnames are re-checked on every request, and redirects are no longer followed: a 3xx response now fails the run or delivery — see Outbound request policy
  • Organizations can require two-factor authentication — an owner can require an authenticator app for every member except owners under Admin → Settings → Security. Members without it can’t use the organization’s projects in the dashboard, CLI, or MCP server, or create personal API keys; SDK ingest is unaffected — see Organizations
  • Bedrock role mode sends a per-project External ID — copy it from Settings → LLM Connections → Add connection → Cloud provider → Amazon Bedrock → Assume role and add it to your role’s trust policy. A role that requires a different External ID fails until you update it — see LLM Connections → Bedrock: assume role
  • Stricter Vertex AI and Bedrock settings — Vertex AI service accounts must be plain service_account keys (other credential types are rejected and extra fields are dropped), and the Bedrock region and Vertex location are validated
  • ZespanPilot and issue suggestions no longer render images, and links in them are limited to https, mailto, and relative URLs
  • Prompt variable values are inserted literally — placeholders inside a value are no longer expanded
  • Self-hosting: new LLM_CONNECTIONS_ALLOW_PRIVATE setting lets LLM connections reach private endpoints you operate, and LLM_CONNECTION_SECRET is now required in production — see Environment variables

Privacy

  • Download your data export and track your privacy requests — request an export from Settings → Privacy, get an email when it’s ready, and download it from the same page (exports expire after 7 days). Each request shows its status, and account deletion explains when it’s blocked because you’re the only owner of an organization — see Privacy
  • Separate analytics, marketing, and advertising choices — each is optional, unticked by default, and can be withdrawn at any time under Privacy choices. Analytics and session replay load only after you opt in, with all text masked, and never when your browser sends Do Not Track or Global Privacy Control
  • Erase or export one end user’s data — by userId or sessionId, per project, from the API. Erased end users’ late events are dropped for 30 days — see End-user data requests
  • Deleting a project stops its API keys immediately and permanently removes its data after a 7-day grace period; deleting an organization purges its trace data within about an hour — see Data retention and deletion
  • Data older than your plan’s retention is now permanently deleted daily; after a downgrade, the previous plan’s retention is kept for 30 days
  • Server-side PII redaction per project — turn on Redact personal data (PII) under project Settings → General and pick a preset. Behaviour change: every project now also masks IBANs, IPv4 and IPv6 addresses, and + international phone numbers, even with the setting off. Evaluators and judges see the masked text, so scores on content containing these values can differ — see PII redaction → Server-side redaction
  • SDK redactPii / redact_pii covers more fields — error messages, tags, metadata, tool arguments, tool results, and tool definitions, in both SDKs
  • IP addresses are stored truncated (/24 for IPv4, /48 for IPv6) for sessions, privacy choices, and new audit log entries

API and SDKs

  • Every API error includes a requestId and message, every response has an x-request-id header, and 429 and 503 responses include Retry-After. Unknown routes return a NOT_FOUND error body — see API reference → Errors
  • Idempotent ingest — POST /v1/ingest accepts an Idempotency-Key header; repeating an accepted request returns replayed: true and the Idempotent-Replayed: true header without ingesting anything again. Ingest and OTLP allow 300 requests per minute per project, and events count against your quota only after they’re queued — see Idempotent ingest
  • More reliable SDK delivery — both SDKs send automatic idempotency keys, at most 100 events per request, time out requests after 30 seconds (configurable), honour Retry-After, keep events in a bounded queue while delivery fails, spend up to 10 seconds delivering at exit, and serialise unusual values safely — see TypeScript and Python
  • Dataset cursor pagination — GET /v1/datasets/{datasetId}/items pages with cursor / nextCursor; getItems / get_items now return every item, and new iterateItems / iter_items stream them. The dataset page shows up to 20,000 items — see Datasets

Guardrails and AI features

  • Guardrail deadlines — the SDKs send x-zespan-deadline-ms with every check, Zespan answers within it, and AI rules it had no time to run are listed in skippedForDeadline — see SDK guardrails → Deadline
  • Semantic Judge usage is counted per rule evaluated — a check that runs several Semantic Judge rules on Zespan’s AI now counts once per rule. Repeat checks served from the 120-second verdict cache and rules on your own LLM connection don’t count — see Guardrails → AI usage limits
  • Guardrail activity is recorded a few seconds after each check, and stored reasons are redacted with your project’s PII settings
  • The Overview digest is reused for a short while instead of being regenerated on every load, and shows the rule-based summary for about a minute when the AI provider is failing — see Overview
  • Data answers start from a single query — ZespanPilot and the AI Hub answer from one generated query and only compare several candidates when that query fails a check — see ZespanPilot

AI governance

  • New organization setting: AI processing — an owner can turn off Zespan’s own AI features for the organization’s data under Admin → Settings → AI processing. AI features return 403 AI_PROCESSING_DISABLED, summaries fall back to the rule-based version, Semantic Judge rules on Zespan AI credits follow their If the check can’t run setting, and features on your own LLM connection are unaffected — see Organizations → AI processing
  • AI-generated notices — ZespanPilot answers, AI Hub answers, and AI summaries carry an “AI-generated by <model> — may be inaccurate” notice, and the new GET /v1/ai/models lists the model in use for each AI feature
  • ZespanPilot redacts personal data before every model call — see ZespanPilot → Personal data
  • Google ADK respects prompt storage fully — with storePrompts: false / store_prompts=False, both SDKs no longer send ADK tool arguments, tool results, or decision rationale, and ADK error events carry only the error class name — see Google ADK
  • EU AI Act and ISO/IEC 42001 evidence mappings — control evidence documents can now map evidence to the EU AI Act and ISO/IEC 42001 as well as SOC 2, with a Manual evidence required status for obligations Zespan can’t observe, and new AI data governance and record-keeping sections. These documents support your own assessment; they are not a conformity assessment or certification — see Frameworks and controls

Sign-up and onboarding

  • Clearer sign-up — buttons stay enabled, and pressing one without ticking the Terms shows an inline message instead of doing nothing. Sign-up and sign-in can show a See a demo link — see Sign-up and onboarding
  • Resend your verification email from sign-up, sign-in, or the verify-email page (once every 60 seconds), with a clear Your email isn’t verified yet prompt when you sign in before verifying
  • Onboarding suggestions — an organization name from your work email domain, the project name my-first-app, and the development environment, all editable
  • Send a sample trace from onboarding (or POST /v1/projects/{id}/sample-trace) to see a trace before your app is instrumented. Sample traces show a Sample badge and count 3 events toward your quota — see Send a sample trace
  • Advertising measurement covers first-trace events, and advertising attribution is deleted when you withdraw that choice — see Privacy

Audit log and self-hosting

  • Audit log retention, stated accurately — the audit log is append-only, and its retention is one deployment-wide setting rather than following your plan. Automatic deletion of old entries is off unless the operator enables it, and critical security events are never removed by retention — see Audit log → Integrity and retention
  • New audit events for the two-factor requirement, the AI processing setting, and project PII redaction changes
  • Self-hosting: health and monitoring endpoints — GET /health reports the running build, the web app has /healthz, GET /ready checks dependencies, and GET /metrics serves Prometheus metrics behind METRICS_TOKEN — see Self-hosting → Health checks and monitoring
  • Self-hosting: audit settings — new AUDIT_RETENTION_DELETE_ENABLED (off by default) and optional AUDIT_MIRROR_* settings for copying minimised audit records to a separate bucket; S3_AUDIT_BUCKET is deprecated — see Environment variables

Platform updates — AI answers now say when they can’t answer

Released: September 2026
  • Proposed fixes are now compared using the incident’s own tool results — when Zespan checks a draft prompt against an incident’s failing requests, both the current prompt and the draft are now shown the tool calls and results those requests actually received. Previously a request that used tools was compared without them, so a favourable score could reflect your tools behaving differently that day rather than the fix. A comparison where a recorded result was too large to include in full now reports unverified instead of passed — see Incidents → Propose a fix
  • The Evaluations page now shows evaluations that could not run — when a judge times out, errors, or returns an unusable reply, a notice above the summary numbers says how many evaluations were affected in the selected time window, which evaluators they belong to, and the latest error for each, and states that they are not counted as failures or in pass rates. A notice that keeps appearing usually means a problem with the judge’s LLM connection — see Evaluations → When an evaluator can’t run
  • Judge errors and timeouts are no longer recorded as failing scores — when an evaluator’s judge model errored, timed out or returned an unreadable reply, live and session evaluations recorded a failing score, which could raise failure notifications, form issues and pull down pass rates. Those traces are now left unscored, and retroactive evaluation runs report how many traces could not be scored instead of skipping them silently — see Evaluations → Retroactive evaluation. Existing scores recorded this way are not removed automatically; contact support if you want them cleared
  • Guardrails no longer let traffic through when a check fails to run — previously a guardrail that errored or timed out was treated as passed, and a Semantic Judge whose model gave no usable answer allowed the content. This is a default change: a check that can’t run now applies the rule’s own action, so a Block rule blocks and a Redact rule redacts (Warn and Log rules still let the request through with a warning). If you prefer availability over enforcement, choose Allow the request per guardrail under Advanced settings → If the check can’t run — see Guardrails → Creating a guardrail
  • Semantic Judge reads the whole text — it previously judged only the first 5,000 characters, so content after that point was never checked. It now reads up to 20,000 characters on a connection of your own (BYOK) and somewhat less on Zespan’s built-in model, and longer text follows the If the check can’t run setting instead of being judged on a partial read
  • Policies with failOpen: true apply the same setting to their guardrails — both in Zespan and in the SDK’s local evaluation
  • Incident causes are only stated when the evidence supports them — an incident’s root cause is now checked against your data before it is shown. It appears as Verified only when a change happened before the incident was detected and a metric moved after it. Otherwise it is labelled Possible cause, not confirmed, or the incident says Not enough evidence and states what was detected instead. When the AI is unavailable, the incident says AI analysis unavailable and concludes nothing. Recommended fixes are only generated once a cause is verified. Incidents created before this release keep their old wording until they are re-analyzed — see Incidents → Root cause analysis
  • Trace and Issue root cause no longer shows canned advice when the AI is unavailable — the Root cause pane used to show generic advice as a low-confidence diagnosis whenever the AI provider failed, and kept it. It now shows AI analysis unavailable with the trace’s recorded error, and retries on the next request. Issue suggestions no longer count such traces as samples. Root cause for a trace also uses the failing evaluation and the stored input and output where they exist, and drops a cause it can’t tie to them — see Traces → Root cause and Issues
  • Prompt Enhance and the Prompt Optimizer no longer return a template as an AI rewrite — when the AI provider was unavailable, Enhance returned a fixed template as if it were an improved version of your prompt. It now shows AI analysis unavailable and changes nothing. Optimizer suggestions base their confidence on how much verified evidence backs them, rather than defaulting to high — see Prompts
  • The Overview digest falls back to a plain summary, and retries — if the AI can’t produce the digest, a simpler summary built from the same numbers is shown and isn’t kept, so the next load tries the AI again — see Overview
  • An evaluator explanation whose quote can’t be found is now an error, not a kept score — this replaces the earlier behavior of keeping the score and withholding the explanation. The evaluation is recorded as one that could not run, and is left out of scores and pass rates — see Evaluations → When an evaluator can’t run
  • Simulation runs that could not be measured say so, and fail the gate — a run whose judge failed shows Not measured instead of a pass or a fail. A batch-run gate fails when any candidate or baseline run is not measured — see Simulations
  • Datasets built from traces only treat checked output as the right answer — a production output becomes an expected output only if that trace passed its evaluations. AI-written expected outputs are marked AI draft, and simulation gates don’t use them until you approve them in the Items tab — see Datasets
  • Data answers show Verified or Unverified — in ZespanPilot and on the AI Hub’s Natural Language tab, each answer to a question about your data carries a badge showing whether an independent check confirmed the query answers your question. Questions your data can’t answer are declined with a reason, and when the query versions Zespan generated disagree, you are asked for a more specific question — see ZespanPilot
  • ZespanPilot flags figures that don’t match the data — numbers in Pilot’s written answers are checked against the data it retrieved, and a mismatch is flagged as unverified. A sum over a top-N result is never presented as a total
  • Misconfigured guardrails no longer pass everything silently — a rule whose check can’t run because of its configuration (an invalid or over-500-character regex or secret pattern, an invalid scope enforcement pattern, an invalid schema contract, a Semantic Judge pass threshold outside 0 to 1, a PII name detector that is unavailable, or a rate limit store that is unavailable) now follows If the check can’t run. This is a default change: by default the rule’s own action applies, so a rule with a broken pattern may start blocking until you fix it or choose Allow the request. The execution is recorded as one that could not run. Creating or editing a guardrail with an invalid, over-500-character or unsafe regex or secret pattern is now rejected with a 400 error, while existing rules with such a pattern are not changed and fail closed when they run — see Guardrails → Creating a guardrail
  • More accurate PII and prompt-injection detection — PII detection no longer flags plain questions, prices, bare dates, version strings or arbitrary digit runs; card numbers must pass a checksum and a date of birth is detected only when labelled. Prompt injection detection at medium sensitivity now catches persona takeover, system prompt extraction, impersonated system messages and bulk data exfiltration phrasings
  • A prompt quality gate fails when dataset items weren’t measured — an item whose judge returned no score now blocks promotion instead of being skipped, and the gate response reports unmeasuredCandidateItems and unmeasuredBaselineItems — see Prompts → The quality gate
  • New incident type for evaluator errors — an incident now opens when an evaluator’s judge keeps failing on most of its attempts, such as during a judge outage, which previously raised nothing because failed evaluations record no scores — see Incidents
  • Incident analysis can name a newly introduced failing model as a cause — and a model that has been in use for a while is no longer flagged as a new model — see Incidents → Root cause analysis
  • Guardrail checks that couldn’t run are kept out of your numbers — they no longer count as blocks or warnings in guardrail stats, compliance reports or agent compliance scores, and the check result carries an errored flag — see Guardrails and the API reference

Platform updates — Semantic Judge guardrails, and a real fix for guardrails on LangChain and Google ADK agents

Released: September 2026
  • Semantic Judge guardrails can be created again — the custom-prompt LLM judge guardrail type was previously undiscoverable from Add guardrail: it had no template chip and no way to select it from the creation page. It’s no longer deprecated, has its own template chip, and its own configuration UI (judge prompt, pass-threshold slider) — see Guardrails → Creating a guardrail
  • Semantic Judge checks are now metered by plan, with a BYOK path once you exceed it — these checks call a model on every run, so they’re capped at a monthly quota that scales with plan (Solo 100, Pro 1,000, Team 5,000, Scale unlimited). The Guardrails page shows a live usage chip, turning amber then red as you approach the limit, with a direct link to connect your own LLM. Once connected, checks run on your connection instead — unmetered — see Guardrails → AI usage limits and LLM Connections (BYOK)
  • Guardrails now actually block on LangChain and Google ADK agents, not just direct SDK calls — guardrails: true on a LangChain chat model or a Google ADK agent previously ran the check and logged the result, but a block verdict didn’t stop the flagged content from reaching your application: LangChain’s internal callback manager silently swallows errors thrown from trace handlers, and ADK’s callback-based integration had no guardrail check wired in at all. Both are fixed at the SDK level — no guardrail configuration changes needed, just upgrade @zespan/sdk
  • A guardrail’s own enable/disable toggle could silently corrupt its configuration — a partial update to a guardrail (including the dashboard’s own Enable/Disable action) could reset its phase, config, action, priority, or agent scoping back to their original defaults, discarding anything you’d customized since. Fixed; partial updates now change only the fields you actually sent

Platform updates — AI suggestions now show what they are based on

Released: September 2026
  • Evaluators no longer report failures when no provider is connected — a project without an LLM connection was recording a failing score for every evaluator on every trace, which also produced issues and regression datasets that were never real. Nothing is scored until a provider is connected, and the evaluations page now says so. It also names evaluators that are not running because the project has no connection for the provider they score with — for example, a project connected only to OpenAI whose evaluators use a different default provider — and no longer shows “ready to run” while any evaluator is blocked. Existing scores recorded this way are not removed automatically; contact support if you want them cleared
  • Every AI suggestion is checked against your data before you see it — suggestions on Issues, Incidents, Costs and the Overview used to be written straight from a model’s answer. Each statement is now checked against the actual record it refers to, and any statement that does not hold up is not shown. Expect suggestions to be shorter and more specific than before: text that was previously confident but unsupported no longer appears
  • Each suggestion shows the record it rests on — the trace, issue, prompt, policy or metric behind a statement is shown with it and links through where the record has a page, so you can check a suggestion rather than take it on trust
  • A surface with nothing to say now says so — where nothing could be checked against your data you will see “No suggestion: nothing here could be checked against your data” instead of a plausible-sounding guess. This is deliberate: a suggestion you cannot verify is worse than none
  • A suggestion that was cut short is labelled — if a run stops early it is shown as incomplete alongside whatever was verified, so a partial answer is never presented as a finished one
  • Evaluator explanations must quote the text they judged — an evaluator’s written reasoning is checked against the content it scored. Where the quoted text cannot be found, the score is kept and the explanation is withheld rather than shown unverified
  • Guardrails now start on the Solo plan — creating, editing, enabling, disabling and deleting a guardrail, and installing one from a template, previously required Pro. They are available from Solo up, and everything you had already built is unchanged — see Guardrails → Plan requirement
  • Policy-as-Code is enforced consistently everywhere — Policies remains a Pro feature, and that requirement now applies to the API and CLI as well as the dashboard. If you author or apply policies through zespan policy apply or the API on a Free or Solo plan, those calls now return a plan error instead of succeeding. Reading your existing policies is unaffected on every plan — see Policies
  • New organizations start on a 14-day Team trial — every feature and limit on the Team plan, no credit card, nothing charged when it ends. You’ll be reminded 7 days, 2 days, and 1 day before it ends, plus a final email on the day it ends. If you choose Solo or Pro, features that need Team become unavailable until you upgrade again; Team or Scale keeps everything. If you don’t choose a plan, your organization moves to Free — ingestion pauses at the Free limit and Guardrails, Policies, and Evaluations become unavailable, but your projects, members, and existing data are kept and come straight back if you upgrade later. See Your 14-day trial

Platform updates — Zespan now finds failures that never errored

Released: September 2026
  • Issue clustering now sees silent failures — until now an Issue could only form from a trace where a span actually threw. Clustering now also considers traces that failed without erroring: an evaluator scored the output below its threshold (or above it, for evaluators like toxicity where high is bad), an evaluator returned a non-passing verdict outright, a tool call was retried or ran far slower than its siblings, or a behavioural signal fired. This is the class of failure where nothing throws — the agent returns a confident wrong answer and no log line says so — see Issues → What counts as a candidate
  • New behavioural signals: user_retry and agent_loop — user_retry means the same question was asked more than once in one session; agent_loop means one tool was called three or more times inside a single trace. Both are evidence, not verdicts: a person may rephrase for their own reasons and some agents legitimately call a tool repeatedly, so trace detail labels them Evidence, renders them neutrally rather than as failures, and shows no pass/fail threshold — there is no rubric they were scored against — see Traces → Verdicts and evidence
  • The Issues list gained a Cause column, replacing Error code — every issue now names what its traces share: an error code where one exists, otherwise the signal that identified them. It is never blank; an issue with neither reads No cause recorded. Two issues on the same operation with different causes stay separate rather than collapsing into one row — see Issues → Signals
  • Issue clustering keys changed — the grouping key gained a signal component, so it is now (verdict level, primary operation, error code, signal). Existing issues are unaffected; they carry an empty signal and cluster exactly as before

Platform updates — Pricing correction and a new fix-verification gate state

Released: September 2026
  • Billing page corrected to match the enforced plan catalog — this page previously stated Free at 10K traces/month, Solo at $39/mo, and Solo at 1 member; the product has enforced 50K, $29/mo, and 2 members respectively for a while, so those three numbers were simply wrong here. Fixed — see Billing → Plans
  • New gate_unverified run status on Closed-Loop Fix Proposal — a fix-verification run whose score compared favourably but whose replay wasn’t faithful (a tool call’s result went uncaptured, or the project’s retention window had already aged the incident’s tool-call data out) previously reused the gate_failed status, which read as a contradictory “gate failed” headline sitting directly above a “Gate: unverified” line. It now has its own status, still withholding approval exactly like an outright failure — see Incidents → Propose Fix

Platform updates — Superadmin operator actions documented

Released: September 2026
  • Documented what Zespan staff can do to your account — ban/unban (with a required reason and optional expiry), password reset, session revocation, impersonation, and organization suspension have been live in the internal superadmin console but were never written down. Impersonation is time-limited to 30 minutes, fully audited, and shows a persistent banner for as long as it’s active; organization suspension blocks dashboard and API access but never stops SDK ingest — telemetry keeps flowing, so nothing is lost while an account is suspended. Every one of these actions is written to an internal audit log — see Security → Staff access to your account
  • Saving a customised Overview layout no longer fails — the Model Risk widget was offered in the Overview Customise panel (and included in the Leadership, Engineering and Ops starting layouts) but the API did not recognise it, so saving any layout containing it returned Invalid widget ID. Because Model Risk is on by default, this affected the first save for every project. The widget is now accepted, the API’s advertised default layout matches the one the page actually renders, and a rejected save now names the widget it did not recognise instead of failing anonymously — see Overview
  • Locally-evaluated policies now honour appliesTo.models and exemptions — a policy scoped to specific models, or carrying an exemption, was enforced correctly by the server but reached the SDK with that scope stripped: the bundle GET /policy-bundle serves is rebuilt from the deployed guardrail rows, and the rebuild carried appliesTo.agents and nothing else, so the SDK recomputed model scope and exemptions as empty. An SDK evaluating locally therefore blocked requests the server would have allowed — a call on an out-of-scope model, a call reporting no model at all against a model-scoped rule, and any call from an exempted agent. The rebuild now carries every scoping field the row holds, so local and server decisions agree; exemption expiry is still judged at check time, so a lapsed exemption stops suppressing the control on the next call rather than at the next bundle refresh — see Local evaluation

Platform updates — Guardrails page redesign

Released: August 2026
  • Guardrails page redesign — the page’s ten stacked blocks (a 5-card posture hero, a 5-card metrics row, three phase-headed rule grids, and a 10-template “Quick install” grid) are now three: a single status band (posture headline, coverage-gap chips linking straight to adding a guardrail, and a stat line — checks, blocked, redacted, avg check, block rate — with a block-rate sparkline), a Rules table (Rule, Type, Phase, Action, Checks, Blocks, P90, sorted phase then priority, each row with a ⋯ menu for Configure/Enable-Disable/Delete), and two tabs, Rules and Activity (the old Templates tab is gone). Adding a guardrail now always starts from Add guardrail, which opens /guardrails/new — the pipeline diagram that used to sit on the landing page now lives on the individual rule’s detail page, highlighting the rule you’re viewing, and the DB-backed saved presets that used to live in the Templates tab now sit on the /guardrails/new page, below the built-in templates — see Guardrails
  • Guardrails now available on the Pro plan — every guardrail action (create, enable, disable, delete, install from a template) previously returned a 403 requiring the Scale plan even though the page itself rendered for Pro customers; the API now matches the Pro gate the page always advertised — see Guardrails → Plan requirement

SDK updates — August 2026

Released: August 2026 · TypeScript (@zespan/sdk) and Python (zespan) New:
  • Cohere — wrapCohere() (TypeScript) / patch_cohere() (Python) traces the Cohere v2 chat API, including streaming (chatStream() / chat_stream()), tool calls, and cost from usage.billed_units — see Cohere
  • Vectra — ZespanVectraCallbackHandler (both SDKs) implements vectra-js/vectra-py’s duck-typed callback interface directly, tracing ingestion, retrieval, reranking, and generation with no changes to how you call Vectra — see Vectra
  • Documented sending traces from any OTel-instrumented app with no Zespan SDK — the OTLP ingest endpoint (/v1/traces) has accepted standard OpenTelemetry GenAI-semconv traces for a while, but it was never actually documented; frameworks already instrumented with OpenLLMetry, OpenInference, or their own native OTel exporter can now be pointed at Zespan directly — see Send traces from any OTel-instrumented app
  • 9 new per-framework/gateway OTel integration pages — Ollama, DSPy, OpenAI Agents SDK, Strands Agents, Mastra, Agno, and a new Gateways category covering LiteLLM Proxy, Kong AI Gateway, and Cloudflare AI Gateway — each showing that specific framework/gateway’s own OTel auto-instrumentation pointed at Zespan, no Zespan SDK wrapper involved. No-code platforms (n8n, Dify, Langflow) intentionally excluded — none has official OTel support anywhere, only unofficial community patches
Platform:
  • Model detail — a per-model drill-down page — clicking a row on the Models page now opens that model’s own dashboard: daily volume/cost/latency/cache charts, a cost-vs-quality scatter against every other model called in the project with a Cheaper alternatives table (models that cost less per call and score no worse on quality — the direct candidates for “switch this workload here”), reliability (error rate over time, top error codes), a quality tab (average score, pass rate, per-evaluator breakdown, daily trend), and usage broken down by agent and by operation. Quality is computed directly from evaluation_scores grouped by model, matching the pattern already used elsewhere rather than the span-level join the Cost vs Quality page needs. A model with zero calls in the selected range returns an empty found: false shell (200, not 404) instead of erroring — it may simply be unused in that window. Model ids containing a dot or slash round-trip correctly through the page’s URL. Backed by GET /v1/projects/:id/models/:model — see Models → Model detail
  • Policies — author, review, backtest, and apply guardrail policies from the dashboard — a new Policies section (Pro plan, same gate as Guardrails) authors policies as versioned documents instead of files in a repository, and applies them through the identical parseAndValidate → computePlan → applyPolicies pipeline zespan policy apply uses, so a dashboard-authored policy reaches the SDK bundle exactly like a git-authored one. Every guardrail row now carries a three-way sourceKind (git / zespan / dashboard) rather than a boolean, so the dashboard and CI can each prune only the rows they own — a plan computed from either side reports the other’s rows as a foreign_origin conflict rather than adopting or deleting them, and the dashboard has no --force equivalent: taking a policy from the other writer is always the existing, audited Detach action. Policy documents are versioned: an edit creates a new, immutable revision, and exactly one revision is ever applied per document, which is what makes “is this in sync” answerable by reading one row. The inventory shows every policy in the selected environment with its owner, posture, rule count, test status, and sync status; review renders the same plan the CLI’s zespan policy plan prints, with a dialog per refusal (ENVIRONMENT_NOT_ALLOWED, STALE_PLAN, CONFLICTS, REMOVAL_NOT_ALLOWED, ADOPTION_NOT_ALLOWED, UNTESTED_DENY) naming what happened and offering the action that resolves it — UNTESTED_DENY offers Run backtest now as the primary action ahead of the override, matching the CLI’s deliberate speed-bump-not-a-wall design. policy:author is a new, separate grant from policy:apply — editor gains drafting rights but, as before, not deploy rights. Backed by GET/POST /projects/:id/policy-docs and its /validate, /:docId/revisions, /:docId/archive, /plan, /test, and /apply children, plus GET /projects/:id/policy-applies and /:applyId/restore — see Policies, Policy as code, and zespan policy
  • zespan auth login — the CLI signs in as a person, not as a project key — control-plane commands check your role in the organization, which a project API key cannot supply, so the CLI gained a real sign-in: an OAuth 2.0 device authorization grant (RFC 8628) that prints a short code, opens the dashboard’s new /device approval screen, and receives a credential once you approve. An account that has two-factor authentication enabled must re-prove it to approve, enforced on the server rather than only in the page — so a request that skips the screen entirely is refused too; an account with no second factor confirms and approves instead, because requiring enrolment only here would have been stricter than the dashboard, where the same person can already apply enforcement policy without one. Because a device grant is phishable by construction (someone can start a login on their own machine and read you the code), the approval screen shows what the server observed about the terminal that asked — operating system, CLI version, and the IP the request arrived from — reported by the server, never by the link you followed, so a phoned-in code is recognisable as not-your-terminal. Each machine mints a persistent keypair, so signing in again from the same machine keeps the same device identity instead of registering a new one every time, and zespan auth logout keeps the key. Also new: zespan link, which picks an org and project interactively and writes a committable, secret-free .zespan/config.json carrying readable names beside the id (a project has no slug, so a bare UUID tells a human nothing), and zespan projects list, which prints the same data without prompting for CI. Credentials live in ~/.zespan/credentials.json at 0600, re-tightened on every write, and are refused if sent to a host they were not minted against. Breaking: zespan policy pull, test, plan and apply now require a sign-in and refuse an API key outright — they always failed with a 403 server-side, so the practical change is a clear error naming zespan auth login instead of a misleading “you may not be a member of that org”, but any CI recipe passing ZESPAN_API_KEY to those commands was never working and needs rethinking (zespan policy validate is offline and unaffected). Ingest and the data plane are unchanged — /v1/ingest, guardrail checks and zespan doctor still use the API key. Policy applies are now genuinely attributed: the audit trail records the real user, session and device instead of the string "unknown", and a mutation the server cannot attribute to a user is refused rather than recorded against a placeholder. Self-hosters: the API builds the approval URL from NEXT_PUBLIC_APP_URL, which must point at the dashboard origin — see zespan auth, zespan link, zespan policy and CLI overview
  • The audit log names the real actor, not just a user — every audit entry now carries an explicit actor type (user, apiKey, or system) alongside its id, instead of every row implicitly meaning “a user.” An SDK request authenticated with a project API key is attributed to that project, not to whoever created the key; a scheduled job, timer-driven expiry, or other action with no human or key in the loop is attributed to system rather than borrowed from whoever happened to trigger it. GET /v1/orgs/:orgId/audit (and the project-scoped equivalent) render this as an actor object ({ type, id, name, deleted }) and accept a new actorId filter alongside the existing userId filter. The CSV export’s actor column now carries the real actor’s id instead of always a user id, and gains an appended actor_type column so a script parsing the file by column position keeps working — see Audit log
  • New page: Client IP behind a proxy — a deployment constraint self-hosters have to meet, written down rather than assumed. Zespan derives a caller’s address from the proxy chain, so your load balancer, reverse proxy or CDN must overwrite X-Forwarded-For rather than append to whatever the client sent, and the API must not be reachable around it. The page gives the directive per edge (nginx, Caddy, HAProxy, Traefik, Envoy, ingress-nginx, ALB, Cloudflare), a one-request probe to verify it rather than trusting the config, and — the part that matters — what a forgeable address actually costs: the CLI approval screen shows the IP the sign-in came from as corroboration for a human eye against a code read out over the phone, and a chosen address makes that row read as the victim’s own office. Nothing else in the flow leans on it: ownership, the claim, the second factor, revocation and attribution are all checked server-side. Also new on that path: starting a login is rate-limited to 30 codes per 5 minutes per address (polling for the result deliberately is not — one login polls about 120 times), and abandoned grants are swept so nothing outlives roughly 40 minutes. Both need infrastructure that is easy to omit — the rate limit needs REDIS_URL, and the sweep runs only in a worker with no WORKER_TYPE set. Correction: approval requires a second factor from accounts that have one, not from every account — an earlier note here said otherwise
  • Exemptions, appliesTo.environments and appliesTo.models are now enforced — all three were validated and documented but had no effect on anything: a policy could declare an exemption, pass validate, apply cleanly, write an audit row naming it, and still block the exempted agent. Exemptions now suppress the policy’s rules for the agents they name (case-insensitively; an exemption naming no agents covers every agent in the policy’s scope), and a suppressed rule writes no guardrail event rather than one recorded as allowed, which would read as “the control ran and found nothing”. expiresAt is checked on every request, not at apply time — the day named is itself still exempt, compared as a UTC date so the answer does not depend on the server’s timezone, and from the day after the control enforces again on its own with no redeploy. appliesTo.models now scopes a policy per request: a policy scoped to ["gpt-4*"] does not fire for claude-opus-4, and a request reporting no model at all is out of scope rather than blocked, matching the convention model_governance already set. appliesTo.environments is a guard rather than a router — apply refuses (ENVIRONMENT_NOT_ALLOWED, naming every offending policy, checked before every other refusal) to write a policy into an environment the policy does not list, and never expands one apply into several, because routing silently to environments nobody named on the command line is how a policy reaches production unintentionally. The guard compares against the resolved environment, so --env production still satisfies a policy listing prod. Renewing an exemption or changing appliesTo.models is a real change, so zespan policy plan reports it as an update instead of a silent no-op — see file reference, Policy as code and zespan policy
  • Locally-evaluated policy decisions now reach the dashboard — a request blocked in-process by PolicyBundleStore never reached the server, so a policy evaluated locally showed zero hits while blocking continuously; the faster local evaluation was adopted, the blinder the dashboard became. The SDK now batches its local verdicts and reports them to a new POST /v1/policy-events (API-key authenticated, answering 202 rather than 200 because the write is fire-and-forget), landing them as ordinary guardrail events marked evaluated_locally so a silently broken SDK never looks like a quiet policy. Reporting cannot hurt the host process: the queue is bounded and drops rather than buffering under backpressure, a failed or non-2xx send is counted as dropped and never requeued, and nothing on the path throws or adds latency to evaluate(). Drops and sends are exposed as reportsDropped / reportsSent on getStatus(). The drain piggybacks on refresh(), with flushReports() as the escape hatch for a short-lived process that will not refresh again and would otherwise exit with events still queued. Every report carries the rule’s label, action, policy id and enforcement mode — never the matched excerpt; the trace already holds the content under your existing redaction. guardrail_events gained policy_id, enforcement and evaluated_locally, so per-policy analytics work and a dryrun-suppressed block stays distinguishable from a rule genuinely authored as warn — see Local evaluation
  • Policy testing and the deny gate — zespan policy test --against issues|last:7d|dataset:<id> backtests a policy over your project’s own recorded traffic and reports what it would have caught against what it would have broken. The false-positive count is broken down per rule, not just per policy, so a single noisy rule in a stack is identifiable rather than forcing you to abandon the whole policy — and because Zespan holds the trace, each false positive comes with the actual conversation rather than an estimated aggregate rate. Prompts and completions are separate samples, so a pre-phase rule is never scored against text the model produced and never sees in production. Promoting a policy to enforcement: deny now requires either a test result or an explicit --untested; the result is keyed to the policy’s exact content, so editing a rule invalidates it. Deliberately not a hard block — a platform engineer mid-incident has to be able to ship — but the default path makes you look at the false positives first. Every report carries the note that a backtest only catches what is in the corpus and cannot catch attack patterns that have not happened yet, which is why warn against live traffic remains the second line of defence — see Policy testing
  • Local policy evaluation in the SDK — a new PolicyBundleStore downloads a compiled policy bundle from GET /v1/policy-bundle and evaluates the rules that are pure functions (regex, secret_egress, model_governance, topic_boundary) in-process, instead of every guardrail check being a network round-trip as it is today. Failure semantics follow OPA’s bundle model: fail closed until the first-ever bundle activation (answering “allowed” from an empty bundle is indistinguishable from having no guardrails), fail open on every disconnect after (a control-plane blip must not become a fleet-wide false deny), staleness exposed as four separate timestamps rather than one boolean so “could not reach the control plane” stays distinguishable from “reached it and got something unusable”, and no half-applied state — a bundle is swapped in whole or not at all, so revision N keeps serving while N+1 is unavailable. The bundle declares what it does not carry, so a policy using any server-evaluated type makes the SDK call out rather than answer from a partial picture; whether an unevaluable rule calls out or is skipped is taken per-policy from spec.failOpen. Because the SDK cannot import server code, its evaluators are a second implementation — a parity test runs identical inputs through both on every build and fails if they ever disagree — see Local evaluation
  • Policy as code: adoption, review screen, and three new primitives — zespan policy pull generates policy files from the guardrails a project already has, so moving an existing project to policy-as-code no longer means hand-writing a file per rule. Each generated file pins its rule to the existing guardrail, so the plan straight afterwards reports no changes to make — only pending adoptions — and zespan policy apply --adopt takes each guardrail over in place, keeping its id so execution history and metrics survive the handover. Generation is deliberately not lossless and says so at the time: failOpen is assumed, and a non-default maxLatencyMs, a template link, a disabled rule, or a slug the format cannot express each raise a warning (the last is skipped rather than written as a file that would fail validation). A new Review changes screen on the Guardrails page renders the same change set as the CLI from the same endpoint, and is read-only — reviewing needs policy:read, applying still needs policy:apply. Three new rule types: model_governance (allow/deny model globs, requireProvider, data-residency regions — a model whose provider cannot be inferred fails rather than being guessed, and an allowRegions rule with no reported region fails rather than passing unchecked), secret_egress (a maintained credential pattern set covering AWS, GitHub incl. fine-grained PATs, Slack, Stripe, OpenAI, Anthropic, Google, SendGrid, Twilio, npm, PEM keys, JWTs and bearer tokens, with disablePatterns/additionalPatterns escape hatches), and schema_contract (full JSON Schema validation of output, not just “is it JSON”). Rules may now carry an explicit slug, which is what makes adoption a no-op. Backed by GET /v1/projects/:id/policies/pull and an allowAdopt flag on apply — see Policy as code, file reference, and zespan policy
  • Policy as code — guardrail policies can now be authored as YAML files in your repository and applied with a plan / apply loop, via a new zespan policy CLI subcommand (init, validate, plan, diff, apply). validate runs fully offline — no API key, no network call — so it is safe as a pre-commit hook, and it reports every problem in every file at once rather than one per run. Ownership is explicit: a guardrail is owned either by a policy file or by the dashboard, apply only ever updates or removes rows it owns (so a first apply from a partial directory cannot delete dashboard-authored guardrails), and code-owned guardrails render read-only in the dashboard with a Code-managed badge and a Detach action that hands ownership back. apply refuses in three cases rather than surprising you, each naming the flag that resolves it: a stale plan, a policy detached in the UI, and any removal (which needs --allow-remove, because removing a guardrail removes a control). A new spec.enforcement ladder — dryrun → warn → deny — moves a whole policy’s posture in one line and compiles down onto the existing engine with no change to how checks are evaluated. Comparison runs over a canonical form, so reformatting a file, reordering keys or adding comments is not a diff. Every apply records each file’s SHA-256 content hash to policy.applied in the audit log. policy:apply is granted to owner and admin only. Policy files parse as YAML 1.2 core (so on/no stay strings), with anchors, aliases, merge keys, tags and multi-document files rejected by name and line. Backed by GET /v1/projects/:id/policies, POST .../policies/validate, .../plan, .../apply and .../:policyId/detach. Not in this release: policy pull (generating files from guardrails you already created in the dashboard), a dashboard review-changes screen, and policy testing against recorded traffic — see Policy as code, file reference, and zespan policy
  • zespan doctor — a new subcommand on the @zespan/cli binary (zespan doctor, alongside the existing zespan gate / zespan-gate) diagnoses SDK setup problems in one command: API key validity, API reachability, whether spans are actually arriving (GET /v1/projects/:id/ingest-health), your @zespan/sdk version against latest, PII redaction posture, and a heuristic scan for the most common integration mistake — a provider client (OpenAI, Anthropic, Mistral, Groq, Cohere, GoogleGenAI) constructed before zespan.init() runs, so it never gets patched. Configure via flags, ZESPAN_* environment variables, or a committed .zespan.yaml — neither the API key nor the API base URL is ever read from that file, so a committed config can’t redirect where your key gets sent. doctor also flags a --project/ZESPAN_PROJECT_ID that doesn’t match the project your API key belongs to, and doctor --help prints usage without making any network calls. --json emits { results, summary } for CI. A new GET /v1/sdk/whoami endpoint backs it, resolving project identity from an API key alone — see zespan doctor and CLI overview
  • Environments — every project now has real environment records (seeded dev/staging/prod, plus any custom slugs you add) instead of just a free-text tag: manage them from Settings → Environments, switch between them from a new project-header switcher that scopes Traces, Evaluations, Guardrails, Incidents, and metrics via a shared environment query parameter, and delete is blocked with a 409 naming exactly what’s still attached rather than orphaning guardrails, alerts, or incidents. Historical free-text values (production, development) alias onto prod/dev on both read and ingest; an inbound value that matches neither a slug nor an alias is never dropped, only flagged — see Environments
  • Changes — a new project-wide timeline page merges prompt deploys, agent lifecycle transitions, guardrail/evaluator/alert edits, and deploys reported from your own CI pipeline into a single chronological feed, filterable by time range and change kind. Report your own pipeline deploys with a new POST /v1/projects/:id/changes endpoint so they show up alongside everything else — see Changes
  • Changes around this incident — an incident’s detail page now shows a before/after split of every prompt deploy, agent lifecycle transition, and policy/evaluator/alert edit in the six hours on either side of when the incident started, each side labeled with its count so the split is legible at a glance. A partial-results banner appears if any underlying source didn’t respond in time — see Changes around this incident
  • Blast Radius — a dependency graph across prompts, agents, models, guardrail policies, evaluators, and alerts, distinguishing declared (configured) from observed (actually-called, from real trace data) edges. Surfaces as an advisory impact summary in the prompt release-confirmation dialog and as a delete-blocking check on evaluators — deleting an evaluator with dependents now requires explicit acknowledgement instead of silently breaking whatever depended on it. No standalone page or graph visualization yet; both are on the roadmap. GET /v1/projects/:id/blast-radius and GET /v1/projects/:id/graph back it — see Blast Radius
  • Outcome attribution — a new Value dashboard page shows which agents and models actually drive business results. Report a business outcome (a deflected ticket, an avoided refund, an SLA met) against any trace with zespan.getClient().outcome(...) (TypeScript) or POST /v1/ingest/outcomes directly (Python, or any backend) — reporting is designed to happen out-of-band, minutes or hours after the trace that produced the outcome has finished. Outcomes are summarized per-agent and per-model against real trace cost: total outcomes, success rate, attributed value, cost, cost-per-success, and value-per-dollar. Re-reporting the same outcome corrects it rather than double-counting it. GET /v1/projects/:id/outcomes/summary and GET /v1/projects/:id/outcomes/kinds back it — see Value
  • Compliance Evidence Packs — a new Compliance dashboard page generates audit-ready evidence documents from data Zespan already recorded: a per-agent Compliance Card, or a SOC 2 control evidence report mapped to CC6.1 (logical access), CC7.2 (monitoring), and CC8.1 (change management). Every document is hash-addressed (SHA-256 over the exact stored bytes) and re-verifiable — a new GET /v1/projects/:id/evidence-packs/:packId/verify re-hashes the stored document and re-resolves every cited record against live data, honestly separating what it actually re-checked (checkedRefs/evidenceStillPresent) from what it structurally can’t (unverifiableRefs, e.g. ClickHouse aggregates). A coverage preview shows which controls have evidence for your chosen period before you generate anything, and every document carries a disclaimer stating it reports observed evidence, not a certification of compliance. SOC 2 is the only framework mapped today — EU AI Act and ISO/IEC 42001 aren’t yet — and the SOC 2 mapping itself is reviewed internally, not yet by a licensed external auditor, which the document says on its face. Output is JSON or a printable HTML document; PDF export isn’t available yet (print-to-PDF from your browser is the interim path), and there’s no recurring/scheduled generation or email delivery in this release — generation is on-demand only, from the dashboard or POST /v1/projects/:id/evidence-packs — see Evidence Packs
  • Model Lifecycle — a daily scan matches the models you actually call against a curated, bundled catalogue of provider-announced deprecation and retirement dates, and raises a finding — call volume, cost, affected agents and prompts, all measured from your own traffic over the trailing 30 days — for any model retiring within 90 days that you’re still calling. Findings surface on a new Overview widget, a new Lifecycle column on the Models page, and a dedicated Model Lifecycle findings page. Dismissing a finding suppresses it at its current urgency band (90/30/7/0 days remaining) and it re-raises exactly once when the deadline tightens into a nearer band, never when a feed correction pushes it further out. Delivery reuses the existing alert-rule/webhook machinery under a new model-lifecycle rule type — a project needs an enabled rule of that type to get notified; the finding is recorded and visible either way. There is deliberately no fabricated quality delta or regression comparison against the named successor model — the only automated comparison is a measured cost-per-call difference on your own organic traffic, and the UI states outright that Zespan does not run your evaluations against the successor for you. Drift detection (catching a pinned model’s behavior silently changing over time, independent of any provider announcement) is not part of this release — see Model Lifecycle, Models, Alerts, and the feed reference
  • Organisation view for Policies — every project’s policy inventory, rolled up — a new Organisation view on the top-level Policies entry in the org admin sidebar answers “is this policy deployed everywhere it should be?” across the whole org instead of one project at a time: four rollup cards (policies, projects covered, occurrences at deny, untested-or-stale), grouped by policy id with a posture/owner/attention breakdown per group, and a Recent applies feed across every project. The Environment scope selector defaults to all environments, not production — Production and any single environment are explicit narrowings. Coverage renders as “present in 9 of 12 projects” and stays a fact, never a verdict: no standard yet exists in Zespan for a project to be measured against, so nothing here calls a project non-compliant or failing. Apply recency is bounded to a trailing 90-day window and the most recent 2,000 applies org-wide, reported back as applyRecencyTruncated/applyRecencyCap — a null last-applied time means no apply landed in that window, not that the policy was never applied. Backed by two new read endpoints, GET /v1/orgs/{orgId}/policies and GET /v1/orgs/{orgId}/policy-applies, both gated on policy:read — see Policies → Organisation view
Bug fixes:
  • ZespanPilot now asks before every write, not just high-risk ones — sixteen actions that change state were executing silently: snoozing or modifying an alert, acknowledging, updating, resolving or writing a postmortem on an incident, adding a guardrail keyword, running or scheduling an evaluation, creating a budget alert, setting a budget, updating project settings, inviting a teammate, setting an SDK concurrency limit, and the Setup Monitoring and Harden Project composites. They were rated low or medium risk, and only high and critical risk triggered a confirmation card. Every one of them now shows the card before it runs. Nothing that previously asked for confirmation stopped asking; the read actions (listing, diffing, and root-cause analysis) are unchanged and still run straight away — see Safety gates
  • Model Lifecycle deprecation notifications can now actually be turned on — a small settings card at the top of the Model Lifecycle page lets you opt in to email and/or webhook notifications for new or re-raised findings. Previously there was no way, in the dashboard or the API, to create the model-lifecycle alert rule the delivery worker looks for, so notifications never fired for any project even though findings were always fully recorded and visible — see Model Lifecycle → Getting notified
  • The OTel ingest endpoint now reads the real GenAI semantic-convention model attributes (gen_ai.request.model / gen_ai.response.model) — it previously only checked a non-standard gen_ai.model, so any spec-compliant instrumentation (OpenLLMetry, OpenInference, and most framework-native OTel exporters) sending traces straight to /v1/traces showed every span as model: unknown — see Send traces from any OTel-instrumented app
  • OpenRouter used via the openai client is no longer mislabeled openai — autopatch() in both SDKs now inspects the client’s base_url and correctly tags these calls provider: "openrouter", fixing provider-based cost and model lookups for anyone using OpenRouter through an OpenAI-compatible client instead of a dedicated OpenRouter SDK
  • wrapBedrock, wrapMistral, wrapGroq, wrapLiteLLM are now reachable via the zespan convenience object (TypeScript) — zespan.wrapMistral(...)-style usage shown on those integrations’ docs pages previously threw at runtime since only wrapOpenAI/wrapAnthropic/wrapGoogle/wrapGoogleGenAI/wrapOpenRouter were actually attached to it

Platform updates — Simulations CI parity

Released: August 2026
  • Closed-loop fix proposal — on an incident with a single identified causal prompt deploy, a new Propose Fix button generates a fix candidate with the AI Prompt Enhancer grounded in that incident’s own captured failing traces, tests it head-to-head against the current production prompt — both replays scored by your project’s own LLM-judge evaluator — using the same Simulations quality gate, and — only if the candidate beats production — routes it through ZespanPilot’s approval queue, showing the gate’s score deltas and regressions alongside the request. Approving lets the reviewer promote the fix to staging or production; rejecting leaves the draft untouched. The draft is created with no labels at all, so it is never reachable by a latest prompt lookup before approval. Only a higher-is-better judge is used for the comparison — a lower-is-better rubric like toxicity would invert the gate — falling back to a provisioned reference-free quality judge otherwise. Starting a run requires both incidents:manage and prompts:manage (owner/admin), plus at least one other owner/admin in the organization, since nobody can approve their own fix candidate. Nothing is ever auto-deployed — a human approval is always required — see Propose Fix
  • Convert to Scenario — a Convert to Scenario button on a trace’s detail page and a session’s detail page turns that one trace or conversation into a regression test in one click: a dedicated one-item dataset named after the source (with a timestamp), and a new scenario pointed at it with Reference Match set to Fuzzy match by default. A trace converts to a Prompt Target scenario, a session (inherently multi-turn) to a Conversation (multi-turn) scenario. It doesn’t auto-run — you land on the Simulations page with a success toast. The new scenario’s provider/model default to the project’s baseline rather than the original trace’s, with the original noted in the scenario’s description — see Convert to Scenario
  • Adaptive adversarial persona presets — the Persona section of a Conversation (multi-turn) scenario now offers four built-in presets (Prompt Injection Attacker, Escalating Angry Customer, Social Engineer, Confused Novice) that populate the existing Name/Goal/Tone/Expertise fields — still editable afterward — plus a tactic-ladder instruction that pushes the simulated user to vary its approach across turns. This works on every run with no evaluator required; when a real judge rubric is also attached, each turn’s judge verdict is additionally prepended to the target’s reply before the next turn, reinforcing the adaptive signal for free — see Adversarial persona presets
  • Real LLM-judge scoring for simulation runs — a simulation scenario’s attached evaluator now scores with a real judge call when its template has a system prompt: one call for a single-turn (Prompt/HTTP) target, or for a Conversation (multi-turn) target, one call per turn plus a final holistic call judging the whole transcript, with each turn’s judge cost and latency shown in the Conversation Transcript. An evaluator with no system prompt (e.g. one created via the quick New Evaluator dialog) still falls back to the existing metric-key/assertion scoring — no judge call, no LLM cost — see Creating a scenario
  • CI gate for simulation batch runs — POST /projects/{id}/simulations/batch-runs/{batchRunId}/gate gates a candidate batch run against an explicit baseline batch run, mirroring the prompt-version quality gate: three threshold signals (avg score drop, regressed items, pass-rate drop, all with defaults), a 202 { pending: true } response while the candidate is still running, and a pass/fail verdict persisted into the batch run’s experimentMetadata.gateResult — see CI gate for batch runs
  • referenceMatch scenario evaluation option — a scenario’s evaluation config can now request exact or fuzzy (≥80% similarity) string comparison against the dataset item’s expectedOutput, surfaced as a reference_match entry in the run’s assertion results and automatically skipped when the dataset item has no expectedOutput — see Creating a scenario
  • Gate Check UI — the CI gate for batch runs is now also runnable from the dashboard, not just the API/CI: a Gate Check card on a completed batch run’s detail page picks a baseline (defaulting to the most recent completed batch run on the same dataset), exposes the three threshold overrides behind a collapsed Advanced thresholds section, and shows the pass/fail verdict, exit reason, and a per-item regression table — persisted so it’s shown automatically on reload, with a Re-run option — see CI gate for batch runs
  • Scenario editing — scenarios can now be edited after creation from an Edit button on the Scenarios list, which reopens the same form pre-filled with the scenario’s current config — see Creating a scenario
  • HTTP Target headers & timeout — a scenario’s HTTP Target now supports custom request headers and a configurable request timeout (defaults to 30000ms) — see Creating a scenario
  • Conversation stop conditions — a Conversation (multi-turn) scenario can now also stop early on a regex match against the agent’s reply, or on its attached evaluator’s pass/fail verdict, alongside the existing max-turns limit — see Creating a scenario
  • Evaluator score direction — set whether a higher or lower judge score counts as “better” for an LLM-judge evaluator via the new Direction button on its row (Evaluators tab); defaults to higher-is-better, with lower-is-better for metrics like toxicity or hallucination rate — see Score direction

Platform updates — Evaluation trust & CI gating

Released: July 2026
  • Evaluations page redesign — the Evaluators tab now shows a color-coded type badge (LLM JUDGE/CLASSIFIER/METRIC CHECK/PATTERN DETECT) and a pass/warn/fail score-distribution bar per evaluator, backed by real per-score counts rather than the metric-level average used elsewhere on the page; the Trends density view’s sparkline now reflects the selected time window instead of a fixed lookback. The Runs tab’s Score and new Samples columns now show each run’s actual result instead of a placeholder, and its status badge gained an icon. The LLM-connection banner also shows a “connected” state naming the active provider once one is configured — see Reading evaluation results and Evaluation runs
  • Vector-DB tracing for Pinecone, Chroma, Weaviate, and Qdrant — calling a vector-DB client directly (no LangChain/LlamaIndex retriever in between) is now auto-traced by zespan.autopatch(), the same one-liner that already covers LLM providers: reads (query/search/near_vector) emit a retriever span with rag_contexts, writes (upsert/add/insert) emit an embedding span with metadata.vector_count — no code changes to your DB client calls. A new recordVectorSearch()/record_vector_search() manual helper covers pgvector and other raw-SQL vector stores, which have no client library to auto-patch — see Pinecone, Chroma, Weaviate, Qdrant, and Manual spans
  • Vector-DB read spans now include metadata.top_k and metadata.filter — every retriever span from the auto-patched Pinecone/Chroma/Qdrant/Weaviate clients now also carries the requested result count (metadata.top_k — always recorded for Pinecone, whose top_k is required; recorded when the caller passes one for Chroma/Qdrant/Weaviate, whose equivalent option is optional) and the query’s filter/where clause (metadata.filter, redacted and stringified, only when storePrompts/store_prompts is on). Both fields are only observable nested under metadata — the ingest pipeline persists the metadata object verbatim but drops other unrecognized top-level fields — see each provider’s integration page for the exact source field per client
  • Corrected built-in evaluators — Latency SLA, Cost Budget, Loop Detection, and Error Recovery Rate now compute a real deterministic check against the trace’s own latency_ms/cost_usd/operation/status data instead of silently falling back to a generic LLM quality-judge score — see Performance & agent evaluators
  • Categorical/boolean scores now render correctly — a categorical or boolean evaluator’s result shows as the judge’s chosen label, or true/false, everywhere it appears (trace detail, per-trace score chip, evaluator tables) instead of a meaningless percentage bar — see Reading evaluation results
  • Unified evaluation score sources — scores attached from the SDK via span.setEvalScore() now appear alongside server-side judge scores in the Evaluations dashboard’s KPI tiles and metric trend list, not only in the raw per-trace view — see Attaching scores from the SDK
  • POST /evaluation-runs now actually scores — creating an evaluation run through the API now enqueues real scoring and the run list shows its aggregated score, instead of the run sitting at pending forever
  • zespan-gate CLI — a new dependency-free @zespan/cli package wraps the prompt quality gate for CI: poll for a pending run, fail fast with a linking snippet if nothing’s scored yet, and exit non-zero on a failed or misconfigured gate — see CI quality gate
  • Evaluation & dataset MCP tools — the hosted Zespan MCP server now exposes datasets, evaluators, evaluation runs, and trace verdicts to any connected coding agent (Claude Desktop, Cursor, Claude Code), alongside the existing trace/metrics and prompt-management tools — see Zespan MCP server
  • Issue, session, retroactive-eval, and cost MCP tools — the hosted Zespan MCP server now covers 35 tools, adding open Issues (list, detail with sample traces, resolve/dismiss, and cached markdown remediation suggestions), sessions (recent-session list, full rollup + turn-by-turn transcript, session-level eval scores), retroactive evaluation runs (create, list, status), and cost attribution by agent/tool/model/user/operation. Write tools enforce the same role permissions and plan limits as the dashboard — a personal API key can only do what its owner’s role allows — and remediation stays advice-only: Zespan never opens a PR or touches your source. See Zespan MCP server
  • HTTP Targets & HTTP-endpoint dataset runs — register an externally-hosted agent endpoint you don’t control or can’t instrument with the SDK (a deployed Bedrock/Glean agent, or any HTTP-reachable chatbot API), then run a dataset against it directly from “Run over dataset” — Zespan POSTs your hydrated request template to the endpoint (with retry, SSRF protection, and W3C traceparent propagation) and records the response as a tagged trace, no LLM connection required — see HTTP Targets and Running against an HTTP endpoint
  • Wider SSRF blocking for HTTP Targets — the address-range check applied to a registered HTTP Target URL (and re-applied to the hydrated URL before every call) now also rejects RFC 6598 carrier-grade NAT space 100.64.0.0/10 — which includes Tailscale’s default range — plus multicast and reserved space from 224.0.0.0 up. The full list of rejected ranges is now documented; there is no override, so an agent reachable only over a private or Tailscale address has to be exposed publicly or instrumented with the SDK instead — see Security: URL validation
  • Retroactive evaluation trigger UI — score historical traces without waiting for online eval to have been configured, from a new Retroactive Runs panel on the Evaluations page: pick any evaluator, a time range, and optional operation/model filters, then watch per-run progress live — see Retroactive evaluation
  • Annotation Queues — a new dashboard section for routing a filtered set of traces to a human reviewer: filter by time range (and, via the API, operation/model/session/verdict), work through pending items with a trace input/output review panel, and submit a pass/fail verdict, optional label, score, and notes. A categorical (label-only) annotation always requires an explicit Pass/Fail choice — there’s no way to submit one without picking a verdict. Annotations are written into the same evaluation_scores data as automated judge scores, tagged human_annotated, so they appear in every existing trend and cost-quality view alongside judge scores — see Annotation Queues
  • Session-level evaluation — a new scope: "session" option on evaluator templates judges an entire conversation’s transcript instead of one trace, so multi-turn quality problems (contradictions, lost context, topic drift) can be caught even when no single call looks wrong. “Session complete” is detected by quiescence — 30 minutes of no new activity by default, checked on a 10-minute scan — since the SDK has no explicit “close this session” call. Results appear as a Conversation: Pass/Fail · N evaluators badge on the session detail page, kept visually distinct from per-trace scores — see Session-level evaluation
  • Session-level scores no longer skew the trace-level Evaluations views — a scope: "session" score judges a whole conversation and belongs on the session detail page, but it was also being counted by the Evaluations page’s metric trends, traces/spans-evaluated tiles, and Retrieval tab. Those four aggregations are now trace-scoped, so a session evaluator can’t drag a per-trace metric average down, add a phantom trace to the evaluated count, or rank session among your worst retrieval operations — see Session-level evaluation
  • Sessions time-range filter — the Sessions list now defaults to the last 7 days instead of scanning full project history on every load, with a date-range picker to widen or narrow the window — see Filtering sessions
  • Issue detail page, resolve action, and remediation suggestions — Issues now open into a detail page with their full sample-trace list; a new Resolve action sits alongside Dismiss with Sentry-style semantics (resolved = fixed, dismissed = stop showing me this pattern — either can reopen on a fresh occurrence); and a Generate suggestion button runs root-cause analysis across representative sample traces and asks ZespanPilot for a cached markdown remediation suggestion — likely cause and one concrete next step, never an automatic PR or repo change — see Issues
  • Multi-turn conversation scenarios are now fully functional — Simulations’ Conversation (multi-turn) target, where a simulated user and your target agent alternate turns, now runs end-to-end. Conversation scenarios also gained structured persona configuration (name, goal, expertise level, and tone compose the simulated user’s system prompt, with the existing free-text Simulated User Prompt as a fallback), and a run’s detail view now shows the full turn-by-turn Conversation Transcript with per-turn latency and cost plus a total conversation cost from real per-model pricing — see Persona configuration for conversation scenarios and Reading run results
  • Conversation scenarios now honor their inner target’s prompt template — the Prompt template you configure for a Conversation (multi-turn) scenario’s inner target was previously discarded, so the target agent ran with no template and no system prompt. It’s now applied to every turn, with {{input}} resolving to that turn’s message, and a configured system prompt is rendered against the run’s variables just like a single-turn prompt target — see Scenario target types
  • Plan retention is now enforced on reads — traces, evaluation scores, guardrail events, and incident events older than your plan’s retention window (14 days on Free through 1 year on Scale) are excluded from every API response and dashboard view. Widening a date filter past the window no longer returns older data — see Data retention and deletion
  • OTLP trace ingest reaches parity with the native SDK path — /v1/traces now enforces the same monthly event quota and 1 MB body limit as /v1/ingest, plus its own 512-span-per-request cap (matching the OpenTelemetry SDK/Collector’s own default max_export_batch_size, rather than reusing /v1/ingest’s 100-span cap, which was sized for the native SDK’s batching and would have silently dropped most spans from a default-configured OTel exporter), with the overflow reported through OTLP’s partialSuccess.rejectedSpans — see OpenTelemetry integration
  • OTLP spans now honor organization custom pricing — spans ingested over OTLP are priced through the same resolver as SDK spans (custom ModelPrice rows first, built-in table as fallback) instead of always using built-in rates. Orgs with negotiated or self-hosted model pricing will see corrected costs on OTLP traces
  • /v1/metrics and /v1/logs now return 501 instead of a misleading 202 — these OTLP signals were never stored; the endpoints previously accepted and discarded the payload while reporting success. They now fail honestly and non-retryably. Point only your trace exporter at Zespan — see OpenTelemetry integration
  • Per-trace sampling in both SDKs — a sampleRate/sample_rate below 1.0 now keeps or drops a whole trace, decided from the trace id, instead of rolling per event. Sampled traces are complete: no orphaned child spans, no wrong span counts, and the same verdict on every service a distributed trace passes through. This also fixes a bug where a trace arriving via OpenTelemetry (or from another service in a distributed trace) always sampled in at 100%, ignoring sampleRate entirely — if you use OTel with sampleRate below 1.0, upgrading will reduce your ingest volume down to your configured rate, which is the fix working as intended, not new data loss — see sampleRate
  • Corrected span counts across the dashboard — agent summaries, the AI Hub top-agents panel, top-cost and slow-trace tables, the ops expensive-traces panel, and session summaries now count distinct spans instead of raw rows, so their span and error totals match the trace detail view
  • Multi-trace root-cause investigation — ZespanPilot can now investigate a recurring Issue across its sample traces instead of one trace at a time, and reports whether the occurrences share a single root cause or are several distinct failure modes colliding on the same verdict/operation/error-code cluster key — so an Issue that is quietly two problems says so — see Automatic remediation suggestions
  • Automatic remediation suggestions — an Issue that recurs three or more times gets a remediation suggestion generated for it without anyone asking, ready on the Issue detail page with the time it was produced. Still text only: no pull request, no prompt edit, no config change — see Automatic remediation suggestions
  • Evaluator authoring from chat — describe what you want scored (“check whether replies stay on topic”) and ZespanPilot maps it to the right built-in metric and creates the evaluator, instead of pointing you at the dashboard — see What ZespanPilot can do
  • ZespanPilot conversations survive navigation — leaving the Pilot page and coming back resumes the same conversation with its transcript instead of silently starting a new one, and reopening a conversation from History now shows its messages — see Opening ZespanPilot
  • Export a dataset run comparison — the two-run comparison view on a dataset’s Runs tab now has Export CSV and Export HTML buttons, so a report can be saved, attached to a PR, or shared without needing dashboard access — see Comparing two runs

Platform updates — Agentic reliability & RAG evaluation

Released: July 2026
  • Enforcement-linked trace forensics — guardrail hits now appear inline in the trace flame graph as their own spans, colored by outcome, instead of a separate log. Promote any violation directly into a permanent guardrail rule with pre-filled tool/field/value — see Promoting a violation to a policy
  • Human approval gates — client.awaitApproval() (TypeScript) / client.await_approval() (Python) blocks execution until an admin approves or rejects the call from a new Approvals inbox — a real human-in-the-loop primitive for your own agents’ high-risk tool calls
  • Near-miss capture & suggested policy rules — numeric guardrail checks that evaluate close to their threshold without firing are now logged; a background worker clusters recurring near-misses into suggested rules you can promote or dismiss
  • Verdict-based issue clustering — a new Issues page automatically groups recurring failed/degraded traces into a single Issue using the same deterministic verdict classifier shown on every trace, with occurrence counts and one-click promotion to a guardrail rule
  • Retroactive evaluations — score historical traces against an evaluator you didn’t have configured at the time, from a new panel on the Costs page
  • Cost-Quality Frontier — a new chart plots every model used on an operation by real cost vs. real quality, span-level joined so multi-model traces aren’t misattributed — see Cost-Quality Frontier
  • Regression testing from production failures — recurring Issues that hit 3+ occurrences are auto-captured into a “Production Failures” dataset your own CI can replay, with verdict-comparison scoring and an optional quality-gate signal — see Regression testing from production failures
  • Agent-to-agent trust ledger (Phase 1: intra-org) — a compliance badge (pass rate, violation count, last violation) now appears on delegation lines in the trace detail view and on the Agent Registry, pulled from anywhere in your org rather than only the current project
  • RAG quality evaluators — four new reference-free evaluator templates (RAG Faithfulness, Context Relevance, Context Utilization, Retrieval Hit Rate) score the retrieval step of a RAG pipeline directly, plus a fix so the existing Groundedness evaluator actually receives retrieved context instead of judging blind — see Evaluating RAG pipelines
  • RAG analytics — a new Retrieval tab on the Evaluations page trends RAG evaluator scores over time and ranks your worst-performing retrieval operations
  • Redesigned trace detail — every trace now opens into a plain-English health verdict, three evaluation lenses (final response / trajectory / per step), and a resizable span navigator + inspector, plus Flow (hierarchical/chronological node graph) and Flame (chronological/icicle) visualizations with full-screen, a Retrieval panel showing retrieved RAG chunks, an A2A task-lifecycle panel, a Session conversation view, and per-span deep-link permalinks — see Traces
  • Bedrock & Vertex LLM connections — bring-your-own-key now covers Amazon Bedrock (static access keys or STS assume-role, no long-lived secret stored) and Google Vertex AI (service-account JSON), usable by Playground, Evaluations, and Simulations just like the direct-provider connections — see LLM Connections

SDK updates — July 2026

Released: July 2026 · TypeScript (@zespan/sdk) and Python (zespan) New:
  • RAG / retrieval tracing — recordRetrieval() / record_retrieval() records a retriever span with the retrieved documents in one call; span.recordDocuments() / record_documents() and a documents argument on span.end() attach chunks to a span you’re timing. Accepts plain strings, objects, or framework nodes (LangChain Document, LlamaIndex NodeWithScore). Chunk text follows your storePrompts and redaction settings. The LangChain and LlamaIndex integrations now capture retrieved document content automatically. See Recording retrieved documents
Bug fixes:
  • Google wrapper no longer crashes generate_content (Python) — a TypeError on the token-usage fields (None where a number was expected) took down every call after the model had already responded; fixed
  • Google embedding cost is now computed — embedding calls that report only a total token count (e.g. gemini-embedding-2) previously showed $0; cost is now calculated from the correct input-token count in both SDKs
  • Manual span kind always recorded (TypeScript) — startSpan({ span_kind }) previously dropped the kind unless the span was created inside an agent context; it’s now always emitted, matching Python

Platform updates — July 2026

Released: July 2026
  • Prompt folders — organize prompts into folders, with a move-to-folder action and folder autocomplete
  • Prompt webhooks — fire a webhook on version created, label assigned, or version deleted, delivered to Slack or a signed, retrying HTTPS endpoint, with a test-delivery button
  • Dataset experiment runs — link your own pipeline’s results to a named run against a dataset via new SDK methods (getItems/get_items, createRun/create_run, run.link()), score runs with an evaluator, and compare two runs side by side
  • MCP prompt tools — the hosted MCP server now exposes prompt management (list, get, create, update tags, set label) as tools an AI assistant can call directly
  • Chat prompt message placeholders — declare a placeholder slot in a chat prompt and fill it with a caller-supplied message array at compile time (e.g. conversation history)
  • SDK prompt-fetch resilience — PromptClient.get() now serves a stale cached value or a caller-supplied fallback instead of throwing when the API is unreachable
  • Custom evaluation templates — author your own LLM-judge rubrics (numeric or categorical scoring), pin a specific judge model per template, and dry-run a template against a real trace before deploying it
  • Auto-evaluator sampling and filters — scope continuous evaluation to a sample rate and filters by model, operation, or status instead of judging every matching trace

SDK v1.0.1 — TypeScript

Released: June 2026 Bug fixes:
  • Fixed API key prefix validation warning (now expects zsp_ prefix)
  • Minor internal reliability improvements

SDK v1.0.0

Released: June 2026 Initial public release of the Zespan SDK (TypeScript and Python).

TypeScript SDK (@zespan/sdk)

  • zespan.init() singleton initialization
  • wrapOpenAI(), wrapAnthropic(), wrapGoogle() provider wrappers
  • wrapOpenRouter(), wrapBedrock(), wrapMistral(), wrapGroq(), wrapLiteLLM()
  • withZespanContext() — async context propagation
  • withAgent() — multi-agent tracing with plan, tool, and handoff spans
  • startSpan() — manual span API with eval score attachment
  • ZespanCallbackHandler for LangChain
  • wrapADKAgent() and wrapADKRunner() for Google ADK
  • PromptClient — fetch, compile, create, and manage versioned prompts
  • GuardrailBlockedError with phase and results properties
  • PII redaction with configurable key list
  • OpenTelemetry dual-export via enableOTel + otelEndpoint

Python SDK (zespan)

  • zespan.init() with autopatch for OpenAI, Anthropic, Gemini, Bedrock, Mistral, Groq
  • wrap_openai(), wrap_anthropic(), wrap_google() explicit wrappers
  • ZespanCallbackHandler for LangChain (sync + async)
  • ZespanADKTracer for Google ADK
  • with_agent() context manager for multi-agent tracing
  • PromptClient with same API as TypeScript version
  • @zespan.trace decorator for sync and async functions
  • FastAPI and Flask middleware
  • Python parity: all TypeScript features available in Python

Middleware v1.0.0

Released: June 2026
  • zespan-autogen — observability middleware for Microsoft AutoGen (agentchat + legacy pyautogen)
  • zespan-crewai — observability listener for CrewAI agents
  • zespan-fastapi — FastAPI middleware for automatic request/response tracing
  • zespan-flask — Flask middleware for automatic request/response tracing