A run that leaves no trace.
An agent-system kernel keeps a record of what it ran. The record is why audit works, why budgets are accurate, why a failed run can be replayed. It is also, for a specific class of workloads, the problem.
A model summarising a candidate CV. A model classifying a medical note. An agent reading a payment-card number so it can confirm a charge. The runtime needs to do the work. The runtime does not need to remember doing it, and in several of these cases is not allowed to.
v1.112.0 ships that posture as a first-class run kind. A no-trace run delivers what it says to its caller and stores none of it.
The short version. A no-trace run passes its intake, its model turn stream, its tool-call bodies, its sub-agent answers and its final text through the loop and never writes any of them to the store. The fact that a run of that kind started is still there, in the token's audit stream and in the budget ledger. The content it saw is not. A no-trace child's answer is withheld from its parent run so the trace cannot leak back in through parent memory, and an unreadable child fails closed. The same release ships restricted tokens that can expire, bind to a set of agents, lock out per-run overrides and rate-limit run starts; a llm:gateway scope that gates and meters the raw model endpoints; strict budget enforcement that stops a run in flight on overshoot; and a one-call run-tree delete behind a new runs:delete scope.
What stops being stored
Every surface the loop writes to for an ordinary run gets a hole punched in it for a no-trace one. Named, one by one, so no one deploys this thinking it is magic.
- The intake. The prompt the caller submitted, the operator header, the attached run metadata, the input blocks the loop was handed. None of this reaches the sessions table or the transcript store.
- The model turn stream. Every chunk the provider streams back (text, reasoning, tool-call deltas) passes through the loop to the caller and nowhere else. The stream event rows that an ordinary run writes do not land.
- The tool-call bodies. A tool call carries an input (the arguments the model computed) and a result (what the tool returned). Both bodies stay in-memory for the length of the call and never reach the tool-calls table.
- The sub-agent answer. A no-trace parent that spawns a child does not get the child's final text written into the parent's transcript, even on the row where the parent cites it, so there is no second copy of the child's work living in the parent record.
- The final text handed to the caller. The run's final answer is streamed to the caller and discarded. The row that an ordinary run keeps with
final_textis empty for a no-trace one.
The runtime does the work the agent was asked to do. When the run ends, the only thing the operator can look up about it is that it ran.
Why the fence runs at the sub-agent too
A parent run that handles sensitive content with care, and a child that handles the same content and writes it to the transcript, is the parent handling it badly in two steps. v1.112.0 ships two pieces that close this.
First, a no-trace parent's children run no-trace by default. The AgentDef flag is inherited down the walk, and a child cannot opt out of its parent's posture. Any attempt to loosen the no-trace bit on a child is refused with a named error.
Second, a child's final text is withheld from its parent's transcript. Ordinarily a parent's record of a sub-agent spawn carries both the parent's own line and a copy of the child's answer, so a reviewer of the parent run alone can see what the sub-agent said. In a no-trace walk the parent record carries the spawn fact and nothing else; the child's answer reached the agent that asked for it, inside the live run, and no further.
Third, a child whose row cannot be read fails the parent closed. In an ordinary walk a child that the parent cannot reach (deleted, revoked, cross-tenant) returns an error and the parent continues. In a no-trace walk the parent assumes a leak and fails the walk. The alternative, continuing on an unverifiable child, is a hole the posture cannot afford.
Who starts one
Three paths, in order of static-ness.
The AgentDef flag. An agent whose definition carries no_trace: true runs no-trace every time, under every caller. This is the right path for an agent whose whole job is sensitive handling (the CV reader, the medical classifier, the card-number confirmer). The flag is part of the content-addressed definition, so a change produces a new version with its own id.
The per-run override. A token that permits per-run overrides can pass no_trace: true at run-start on an ordinary agent. This is the right path for a mixed-use agent whose caller sometimes handles sensitive input and sometimes does not; the caller decides on the call. The override is refused on tokens that lock per-run settings.
The token-level force. A token minted with no_trace: true in its restrictions turns every run it starts into a no-trace run, regardless of the agent and regardless of what the caller asked for. This is the right path for a client machine that must never be trusted to decide for itself. A rotation preserves the restriction.
What still lives
Four things a no-trace run leaves behind, by design, so that budgets stay accurate and the deployment stays operable.
- The fact-of-a-run row. There is still a row in the runs table with the run id, the agent id, the user id, the start and end timestamps and the status. There is no final text, no transcript and no tool bodies. A caller can confirm a run happened and succeeded; an operator can bill for it.
- The budget ledger line. A no-trace run spends tokens like any other. The ledger records input, output and cache tokens with their per-call cost. Budgets still bound spend. A hard budget still refuses a start.
- Metrics counters. The Prometheus series tick for the run (started, succeeded, failed) at the same cardinality as any other. A no-trace run is one row in
loomcycle_runs_total, same as any other. - The token's audit stream. The authentication layer still logs each run start with the token, the subject, the agent id and the time. "This token started 42 runs today under agent X" survives. "What those runs saw" does not.
The feature is "no store of what the run saw," not "no fact of a run existing." The second posture would make the first impossible to operate: an operator who cannot see that a run happened at all cannot bill, cannot allocate capacity and cannot catch a runaway loop.
The release's other content-safety moves
v1.112.0 is the first release in a cluster that moves loomcycle's content-safety story one tier below the audit log. No-trace runs are the headline. Four other pieces sit around them.
Restricted tokens. A token minted for a specific caller can now carry four kinds of restriction. ttl_seconds gives it a fixed life; it stops authenticating when it runs out, and a rotation cannot extend it. agents: [...] binds it to a specific list of agents; any other agent id refuses as restricted_token, whatever the token's scopes. overrides: "none" locks the per-run settings (model, provider, tier, limits, sampling, compaction, hooks) on the agent's declared defaults; a locked setting refuses with override_not_allowed rather than being silently ignored. rate: {per_minute, burst} bounds how often the token starts runs, per user and per replica, with a Retry-After on refusal.
The raw model endpoints are now gated and metered. POST /v1/_llm/chat, /v1/chat/completions and /v1/embeddings call a model with no agent, no tool allowlist and no run row. They are now behind a new llm:gateway scope that admins and tenant operators carry by default; an isolated user's token needs it by name. A call that reports usage is written to the ledger and counts against the caller's budget. A call that ends with no usage report is counted by an estimate from its size; the estimate survives a restart and stays out of the usage ledger.
Strict budget enforcement. A hard budget used to be checked only when a run starts. A run already going, and every sub-agent it spawned, kept spending until the end. LOOMCYCLE_LIMITS_ENFORCE=strict now also checks the budget before each model call of a run in flight, before each sub-agent starts, and when a paused run is resumed. A run stopped this way ends failed with stop_reason set to token_limit. The overshoot is bounded by one model call per run in flight.
Run-tree delete. A new DELETE /v1/runs/{id} behind a new runs:delete scope removes a run and every row that hangs off it (events, tool calls, child runs) in one call. This is the paired tool for a no-trace deployment that wants to clean up the fact-of-a-run rows older than its retention window, in bulk, without a one-table-at-a-time script.
Together these move loomcycle from "trust the operator to not log" to "the runtime physically cannot log, and the token the caller holds cannot ask it to."
Read next
The full release notes are at REVISIONS.md, with the per-PR detail for each piece. The token-restriction fields and their refusal shapes are in docs/CONFIGURATION.md under "Multi-tenant authentication."