The editor page opens an agent's evals over itself, as the agent page does, with the unsaved
edits offered to a run; the editor dialog keeps evals as a level of its own. The two pages share
the dialog.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The agent's own settings (path, labels, workspace specific, description) open from a cog as
the flow and script editors' do, laid out as the top of the resource editor. The folder note is
gone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The path leaves the agent form: the editor dialog opens it as a level, as it does evals, and the
editor page in a drawer. The agent page gains Evals, in a dialog. The page supplies the run
form, keeping it out of the editor the flow editor reaches.
The draft edit gate no longer throws when a focused, changed field is removed: the `change` that
removal fires lands mid-teardown, so the gate opens just after instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drops the agent run-as identity: its storage in the agent value, the backfill, the resolution
on every write, and its handling in export, the CLI, draft and cross-workspace deploys, forks
and the workspace compare. The run endpoint runs as the caller, so an operator or reader runs an
agent with their own access. The editor tells the author of a folder agent that anyone running
it needs access to its AI resource and to what its tools use.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(ai-agent): add autocompacted memory that summarizes older context
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): compact on final-answer turns and count what a turn appended
Address the pre-push review findings on the compaction path:
- A turn the model answers without a tool call left the agent loop on its first
iteration, so a chat-shaped step never compacted and reloaded the whole
conversation on every later turn. Compaction now also runs after the loop.
- The trigger measured only the last request, so a single large tool result
could carry the next one past the window without ever crossing 80%.
- The summarization call re-sent the usage-tracking request shape on endpoints
the loop had already learned to drop it for.
- The flat 8000-token summary reserve swallowed the whole target on a small
context window, leaving one message in the tail and summarizing the rest.
- A response cut off inside the <analysis> scratchpad was accepted as a summary.
- The chat-mode memory default was a shared object the step form edited in place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): keep Anthropic prompt counts and compact once per response
Address the first CI review round on the compaction path:
- Anthropic's streaming parser dropped `message_start`, the only event carrying
the prompt-side counts, so a native Anthropic run reported no input tokens at
all and compaction fell back to a character estimate.
- A loop that exits without issuing another request — a structured-output turn
does — reached the post-loop pass still holding the previous measurement and
compacted a second time, or retried a failure with nothing changed.
- The summarization call inherited the step's `max_completion_tokens`; a low one
truncates the summary inside its scratchpad, which counts as a failure and
disables compaction after three of them.
- A fired trigger that found nothing to summarize said nothing.
- Memory already over the window — a lowered `context_window`, or a step moved
over from `auto` — had no way back, since compaction only ran after an
accepted request. It now also runs once before the first one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): state the summary's own completion cap and drop the pre-flight pass
- The summarization call asked for no completion cap at all, which is "uncapped"
only on the OpenAI-shaped providers: Anthropic substitutes 64000, over several
Claude models' output ceiling, and Bedrock leaves the model's own small default,
short enough to cut the response off inside its scratchpad. It now asks for the
reserve the split already set aside, raised to the step's cap when that is larger.
- Compaction no longer runs before the first request. The fallbacks the loop learns
from a rejection are not known that early, so on exactly the endpoints that need
them the summarization was malformed by construction: it failed, spent a strike,
and the first agent request still carried the oversized conversation. A memory
already past the window is repaired on the turn after a request the endpoint
accepts, rather than by a pass that cannot succeed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): ask the summary for exactly the room the split reserved
The split scales its reserve down on a small window while the request asked for
a flat 8000, so the two diverged below an 80k window: on a 4k/8k model the cap
alone exceeded the window and every summarization was refused, and on a 20k one
a full-length summary could land the conversation back over the trigger and
compact its own previous summary on the next response. Both now read one
`summary_reserve_tokens`.
The call also no longer inherits the step's reasoning effort. Every provider
counts thinking against that same budget, so a high-effort model could spend the
whole reserve before writing anything and return a summary cut off inside its
scratchpad; the compaction prompt asks for an `<analysis>` block, which is the
reasoning this call needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): charge the compaction budget for tools and the system prompt
The tail budget was the whole target, but a request also carries the system
prompt compaction keeps and the tool definitions, which are not in the message
list at all. On a small window those are most of it: a tail sized to the full
target left the next request back over the trigger, compacting again every
response, and the no-usage estimate missed the tool schemas entirely so it could
fail to trigger at all. Both now account for them.
The reserve also gains a floor. It is the summary's output cap as well as the
room the split leaves, and scaled down without one a small window gave a
structured nine-section summary a few hundred tokens — truncated inside its
scratchpad every time, which is discarded, which switches the mode off after
three.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): count Gemini's tool-use prompt tokens in an agent step's usage
Gemini splits a tool-using turn's input across `promptTokenCount` and a disjoint
`toolUsePromptTokenCount`, and its thinking apart from `candidatesTokenCount`.
The agent step's parser read only the headline fields, so every tool-using turn
under-reported both — and the compaction trigger, which runs off the reported
prompt, could not see the tool results that grew it. It now goes through the same
helpers the proxy path already used.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): calibrate the compaction estimate against the measured prompt
Two rounds running, the finding was "the character estimate cannot see input X"
— tool schemas, then S3 attachments, which are short paths in the message list
and whole images by the time a provider counts them. Enumerating those is a list
that only grows, so the estimate is now scaled to the one number that is ground
truth: what the provider charged for the last request. Attachments, tokenizer
drift and whatever comes next fall out of that, because the estimate is only
ever used relative to itself.
Also stop the Gemini helpers turning an absent count into `Some(0)`. Downstream,
absent means "fall back to estimating the conversation" while zero reads as an
empty prompt and would hold the trigger below its threshold for the whole run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): charge attachments what they cost and let a heavy short prefix compact
The calibration conserved the conversation's total cost but spread it by
character count, so an attachment — a short S3 path in the message list, a whole
image or PDF once a provider expands it — was charged to the text messages around
it and stayed nearly free in the split. It now carries a nominal cost of its own,
which the calibration corrects a residual on rather than the whole gap.
The four-message minimum also refused exactly the case that fix is for: an
attachment arriving on the first or second turn can pass the trigger before four
removable messages exist, and summarizing even one of them saves most of the
prompt. A prefix worth a quarter of the window is now enough on its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): never summarize a prefix holding only a previous summary
The message-count floor was carrying a second job: a fresh summary sits in a one
or two message prefix, so requiring four declined it. The share threshold added
last commit admits it, and a summary is reserve-sized by construction — so the
post-compaction shape could spend one summarization per response swapping a
summary for another the same size, shrinking nothing and losing fidelity each
time. A previous summary no longer counts towards that threshold.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): take the context window from the model and drop the estimate calibration
Brings compaction in line with how the AI session does the same job, which had
already answered these three questions.
- The window is looked up from the model. `MODEL_CONTEXT_WINDOWS` in
`windmill-ai/src/model_context.rs` mirrors the session's table in
`copilot/modelConfig.ts`, entry for entry and with the same matching rules;
each side points at the other, since a model added to one and not the other
compacts at two different sizes. A step's `context_window` becomes the
override for what the lookup cannot serve, and chat mode writes none.
- Provider usage is normalized where the provider's quirk is, not at the
consumer. `TokenUsage::with_cache_beside_input` raises `input_tokens` to the
whole prompt for Anthropic and Bedrock, which report their cached prefix
beside it; the OpenAI shape already counts it inside. `prompt_tokens()` is
then just `input_tokens`, rather than inferring the shape from whether a
write count is present.
- The estimator is no longer calibrated against the measured prompt. The
session uses the provider's count when it has one and a chars/4 estimate
otherwise, with nothing in between, and a tail sized a little wrong only
compacts again a turn later.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* feat(ai-agent): summarize memory down to what the database can store
Without an instance object store, memory is a 100KB database row cut
from its oldest message, the summary included, so compaction on a
mainstream model never got to keep anything across runs. A step that
persists there now runs its post-loop compaction pass against the
smaller of the model's window and the cap at chars/4, about 25k tokens:
the loop keeps the whole window, and what is written is a summary plus a
tail that fits. The run logs when that pass summarizes, and how many
messages the write dropped when one still overshoots.
The editor's storage warning on the option is removed: nothing exposes
the instance storage to it, so it keyed on the workspace S3 setting,
which is unrelated to where memory goes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): get a complete, billed summary out of every provider
Compaction against the real providers turned up four things the stub
could not: Gemini and OpenAI's reasoning models think by default and
bill it against the same cap the summary must fit in, so the
summarization request now asks them for their least (none, low); an
OpenAI Responses call that hits max_output_tokens ends in
response.incomplete, whose usage the parser dropped, so that
summarization went unbilled; a summary that quotes </summary> when it
describes its own instruction was cut off at the quote, on the agent
step and the AI session alike; and the prefix could end on an unanswered
user message, after which the instruction reads as part of that turn
(Anthropic merges the two outright). The tail now starts on a user
message, and both prompts tell the model the instruction is not part of
the conversation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): compact down to half the window, on the agent step and the AI session
The gap between the 80% trigger and the target is what one compaction
buys, and every summarization request carries most of the window. At a
70% target a 128k model summarized about 13k tokens of prefix for a
summary of up to 8k, so each ~100k-token request bought a few turns of
room before the next one re-summarized the previous summary. At 50% the
same request frees about 30k.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): drop the workspace-S3 memory hint and state the database bound in the tooltip
The memory field warned that memory is kept in the database whenever the
workspace had no S3 storage. That setting has no bearing on where memory
goes: the instance object store decides, and nothing exposes it to the
editor. The field's tooltip now describes both memory kinds and states
the database bound unconditionally; the run log says what happened.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): send the summarizer its tool history as text
The summarization request carries no tool definitions, and Bedrock's
Converse API rejects toolUse/toolResult blocks that arrive without them,
so on Bedrock every summarization of a prefix holding a tool call failed
silently until the breaker tripped. The prefix's tool calls and results
now reach the summarizer rendered as text, on the agent step and in the
AI session's compaction, which goes through the same proxy.
Also drops the TokenUsage::prompt_tokens accessor, which had become a
plain read of the normalized input_tokens, and shortens the context
window field's description.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): price attachments from the provider count, bound storage in bytes, effort per pro model
Addresses two Codex rounds and a leftovers audit.
- Attachments were priced at a flat 1500 tokens in the split, so a
multi-page PDF (tens of thousands of tokens to the provider, a short
S3 path in the message list) could be kept in the tail or leave no
prefix worth summarizing. They are now priced from the provider's
count for the request that carried them, less that request's text,
with the 1500 floor where nothing was counted.
- The database storage bound measured the provider's token count, but
the 100KB cap is bytes and repetitive text packs several characters
per token. The persist pass now measures the serialized conversation.
- The summarizer forced `low` on every reasoning model, which the pro
variants reject (gpt-5-pro takes only high, gpt-5.2-pro starts at
medium); they now get no effort.
- Dropped the unused prompt_tokens accessor and its orphaned assert, an
unused PartialEq, a needlessly public lookup, and fully-qualified
Gemini calls; refreshed stale comments and the memory_id schema doc;
regenerated the flow schema artifacts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): evict a heavy attachment into the summarized prefix, not the tail
Pricing attachments from the provider count was not enough on its own: a
leading attachment is a user message, and the boundary rule pulled the
last unanswered user turn back into the kept tail to keep it with its
answer. For a heavy attachment that dragged it into the tail — or, at
the front, emptied the prefix — so it was never summarized and rode
every request. The boundary now moves forward instead, keeping that
user turn and its answer in the summarized prefix. Verified on the
running instance: a 25k-token PDF on a 30k window is summarized out on
the turn it overflows, and later turns drop from 26k to ~1.5k tokens.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): keep the forward boundary move off tool results and the prefix start
The forward move that keeps an unanswered user turn out of the tail had
two edges the third Codex round found: advancing past the user could
land the boundary on a tool result (its tool_calls then summarized away,
orphaning it), and with no system prompt the summarizable prefix starts
at 0, so a trigger firing while the tail estimate fit everything indexed
below the start and panicked the task. The forward scan now skips
tool-opening boundaries, and the move is guarded above the prefix start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): drop the step temperature from the summary request
OpenAI's reasoning models (gpt-5-mini, gpt-5.1, gpt-5.2) reject
`temperature` alongside any reasoning effort but their own default, so a
step configured with a temperature made every summarization fail once
the summarizer forced a low effort — history then grew unchecked. The
internal summary call now omits the step's temperature: a structured
extraction does not need a set one, and omitting it sidesteps each
provider's temperature-versus-reasoning rules. Confirmed against the API
that low + temperature is refused on those models.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): compact an oversized loaded memory before the first request
Compaction was reactive, taken only after a request the endpoint
accepted, so the fallbacks the loop learns from a rejection are known
first. But a memory loaded from an earlier run can already exceed this
run's window — the step was switched to a smaller model, or a run under
a wider one persisted more than fits — and that first request then
overflows and fails the run, with every retry reloading the same
history and failing again. A pass is now taken up front, off the
character estimate, before the first request. It uses the default
request shape; an endpoint needing a fallback may reject this one
summary, which is non-fatal, and mainstream providers need none.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): under the storage bound, trigger on the max of bytes and model tokens
The storage-bound pass measured only the serialized row size, so an
attachment — a few bytes as an S3 path but nearly the whole model
context — read as tiny and the pass skipped a compaction the model
needed. It now takes the larger of the byte measure and the model's
token count, since repetitive text is few tokens but many bytes and an
attachment is the reverse; either being over must fire a pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): drop oldest turns when a summary cannot fit the window, as the AI session does
An oversized loaded memory (a step switched to a smaller model, or an
object-store run that persisted more than a later model's window holds)
left a prefix larger than the summarizer's own window, so the summary
request overflowed and failed, the memory was untouched, and every
retry failed the same way. The AI session handles this by falling back
from summarization to dropping the oldest turns down to the target;
compaction here now does the same. When a summary cannot run — it
failed, the breaker is tripped, or nothing is worth folding — the oldest
turns are dropped until the conversation fits and opens on a user
message, keeping the newest turn. The next request then always fits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): drop whole turns only, keep the storage pass to bytes, refresh the count after a rewrite
Three edges the seventh Codex round found, all in the drop-oldest
fallback and the storage-bound measure:
- drop_oldest_to_fit dropped to any point that freed enough, which could
strand a tool result whose tool_calls went with the messages before
it. It now drops whole turns only, always landing the boundary on a
user message and never splitting the newest turn; a lone turn too big
for the window is left whole rather than broken.
- The storage-bound pass measured the whole model prompt against the
shrunk 25k window, so a large tool roster and the system prompt —
neither written to the row — tripped it on a conversation the row
easily held. It measures the serialized bytes alone now; the model's
own window is enforced by the in-loop passes and the pre-first-request
pass, so the persisted size is all this pass is for.
- A compaction rewrites the message list, so the provider's count for
the request that produced it no longer lines up. The count is now
cleared after any pass that rewrites the conversation, so a later pass
measures the estimate over the actual messages instead of a stale,
larger prompt (which could decline a summary that already fit and then
drop it). The step temperature, no longer sent to the summarizer on
any path, is dropped from the request struct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): measure only the persisted messages against the storage cap
Persistence strips the system prompt before writing the memory row, but
the storage pass was serializing every message including it, so a large
system prompt with a tiny conversation reported far over the storage
trigger, and the fallback dropped the one real turn, run after run. The
storage measure now serializes only the non-system messages, matching
what the row actually holds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix(ai-agent): run the model-window pass before the storage-bytes pass post-loop
A turn the model answered without a tool call broke before the in-loop
compaction check, so on database-backed memory its only pass was the
storage one, which measures bytes. An attachment fills the model context
but is a few bytes in the row, so that turn never compacted and a
follow-up could overflow the model. The post-loop now runs a
model-window pass first, off the provider's count, then the
storage-bytes pass when the row is smaller than the model — both limits
enforced for a chat-shaped step, not just the one that happens to bind.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix: simplify agent compaction and preserve execution history
* fix: remove unused compaction history setting
* fix: preserve answers and recover rejected agent context
* refactor: make agent compaction transactional
* fix: skip agent summaries that cannot fit retained context
* fix: explain skipped agent context compaction
* fix: retain recent agent memory when storage compaction cannot fit
* fix: start retained agent memory at a user turn
* fix: reject unsafe agent memory truncation on storage fallback
* docs: clarify agent context window override scope
* fix: keep recent turns verbatim when compaction memory outgrows storage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix: keep the compaction summary out of the agent's answers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* fix: shorten the agent context window help text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH
* chore: update ee-repo-ref to 942d4013f36edac1fc9a9addbdb02198db1c7a05
This commit updates the EE repository reference after PR #812 was merged in windmill-ee-private.
Previous ee-repo-ref: 8ca1682ce6106ba6ea96894fbe606dac64102eb6
New ee-repo-ref: 942d4013f36edac1fc9a9addbdb02198db1c7a05
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: run turns in several flow chat conversations at once
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep finished turns finished and cached chats current in the flow chat pool
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: attribute a turn's rows by job id as well as sequence
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: count only real stream updates and retry the job-id read
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a chat that holds an unsent draft, and take one back when its first turn is withdrawn
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: ignore a stale running-turn snapshot, keep a withdrawn chat's draft, poll after clean stream ends
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: follow the turn running now when the listing named one already over
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep replacement turns and SSE fallback moving
* fix: keep replacement turn handoffs active
* fix: preserve unread badge line height
* fix: settle local fallback handoffs
* fix: settle refused turn handoffs
* fix: scope turn handoffs to conversation
* fix: drop stale turn handoffs
* refactor: move the queued message and 409 handling into per-conversation turns
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address cubic's review of the parallel flow chat turns
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clear a stale failure on refresh, and tighten the docs and test waits
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: recover running rows past the first page, and drop the failure a re-read disproves
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: check the running-turn query at compile time, and narrow what a refresh clears
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: settle a failed turn only from an answer that turn wrote
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: settle a failed turn from its own answer, and only while it is still the failure shown
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop a failure whose answer arrived even when a newer turn owns the error
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: free an answered failure whatever the turn that started meanwhile is doing
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop a rows read that a turn outran, rather than merging it under newer messages
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop a rows read whose conversation was left and opened again
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: hand over a file still being read when its composer goes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: count a drop's routing as work in flight, so its file is handed over too
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: hold the send until every file a conversation is owed has landed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: keep a panel mounted per conversation instead of handing its draft over
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the withdrawn chat whose composer was written in, not the empty one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the chat in front of the reader when both withdrawn composers were written in
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a retry's own run arguments when a turn elsewhere refuses it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: name panels apart across pools, and read a flow's inputs when its chat is built
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: shrink the module graph that gates first paint in dev
* docs: drop the stale synchronous-icons claim on the import card
* fix: replay search opened before its modal loads, guard lazy icons
* fix: only intercept search before load where the modal mounts
* perf: mount app-shell modals on first open and keep monaco off the shell
* perf: load the icon map on first read, not at module evaluation
* fix: report stale chunks with a reload toast, guard the home page against monaco
* fix: hide tool controls a nested AI agent cannot use
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor: drop the tool navigation prop an agent tool now implies
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: ask for a tool name on every kind of agent tool
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: keep enabled_tools reachable on a linked nested agent tool
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: name the tool kinds the header's name field actually renders
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: state the tool-name rule without listing the kinds it covers
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The identity an agent runs as lives in `value.on_behalf_of` instead of a column. Every write
of an agent resolves it server-side as a flow's is: the writer's own, unless an admin or
wm_deployers member asks to keep it, and a folder default on create. Retyping a resource into
an agent resolves its value the same way. Export leaves it out; the run endpoint reads it and
drops it from the step's inputs.
The CLI follows the app model: a pushed agent never takes its identity from the tracked file,
an unchanged agent compares equal to the deployed one, and an admin or deployer push claims
the deployed identity back. The owner-change pre-check lists agents.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: support aiagent steps in test_run_step
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: offer the agent's tools as a picker in the step run form
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: run a blank tool list as no tools when the step form is bypassed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: show restart from failed step in run page top bar
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep failed-step restart button when selection is not restartable
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: add restart link to the failed run status line
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: use the input error color for the progress bar error state
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: mention restarting on a fixed flow version in the error line
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: show a chevron on the restart button when it opens a picker
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: shrink run page top bar buttons so the bar fits on one line
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: target the step that failed the run, not a tolerated failure
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: address review nits on the restart link
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: skip canceled runs and match the restart link label to its text
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: render a tool's web search result as the session web search card
Any tool whose result is exactly `{ sources: [{ url, title? }], query? }`
now renders as the card the AI session shows for a provider-side web
search, in the agent step trace and in the flow chat. The provider-side
search itself gets the same card on both surfaces: its citations, which
the worker records on the assistant turn that follows, move onto the
search row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the trace tool label off the lazily fetched job
The web search card's label read the tool's job, which is only fetched
once the row is expanded, so the query suffix appeared only after the
row had been opened. The shape of the result decides the label; the
job's failure still decides whether the sources or the error show.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep unrenderable sources and tool hostnames out of the search card
A result whose urls the card cannot render — relative, or javascript: —
matched the shape and replaced the tool's own output with an empty list,
so the predicate now requires what the renderer accepts. A tool's sources
can name internal services, so they no longer resolve a favicon through
Google's service; provider-side search results still do. An absent
optional reaches JSON as null from a Python tool, which now reads as
absent rather than rejecting the whole result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: name the host when a source carries a blank title
A source whose title is an empty string rendered as a link with no text
at all, the empty title also suppressing the hostname beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: chat with a saved agent from the agent editor
* fix: keep agent chats apart from a same-path flow's conversations
* fix: point an agent editor chat's model gap at the agent, not a step
* fix: match the memory gate's icon to the chat's empty state
* perf: skip whole-document menu lookup on pointerdown when closed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: load only a session's own chats in its chat history
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: stop mounting a chat per listed session on the sessions page
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep unsent input, init and chat rotation safe from runtime eviction
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: cap mounted chat panes instead of evicting session runtimes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: read only message count and error for sessions without a runtime
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a chat pane mounted while its composer reads attachments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: hold a session's first send until its chat has been restored
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: queue a second send behind one waiting on the chat restore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: queue raw-app inline prompts behind a send waiting on the restore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a chat pane mounted while its send waits on the restore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: preserve session chat drafts across async updates
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: edit variables, resources and triggers in their own session tab
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: take a trigger's flow kind from its config and name the written trigger
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: one operating-workspace context for editors acting on a session's workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reload page item tabs only in sessions acting on the tool's workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve open in workspace for every preview tab kind in one place
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: guard every component under a session editor against the navigation workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show a page item tab whose item is gone instead of mounting its editor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: follow a saved page item in the tab's own workspace and restore in-frame rows
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: close a page item tab from its editor header
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: judge trigger permissions by the user acting in the session workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: judge every session-editor permission by the user acting in that workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep session permissions reactive to the acting role and restore tabs as last seen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: default Path's owner controls to the user acting in its workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix: key the large root font size on screen width, not window width
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: move the flow preview button offset to the screen-width query
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: render an AI agent result as its answer, not as raw JSON
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: sanitize agent markdown through the shared plugin chain
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: fold agent stream events incrementally per poll
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: separate the agent meta line from the result toggle group
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: add a transcript view of an agent run's conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: retire AIAgentLogViewer in favour of the transcript
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: show the partial transcript a max-iterations failure carries
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: move the agent meta line and system prompt below the conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: show an agent run as what it did, not as a conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: name the agent run breakdown a trace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai-agent): label the save-as-agent form fields per the guidelines
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai-agent): reuse the resource form's path and description fields
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai-agent): keep the action tags on a max-iterations failure
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai-agent): keep offline replay inert and the streamed answer to one turn
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai-agent): reset the streamed answer on providers that skip tool_call
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: carry the event type narrowing through the stream parser
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: survive a malformed message rather than take the viewer down
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: coerce agent messages once at the parse boundary
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: show an agent run as one scroll ending in its output
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep every streamed turn instead of dropping the narration
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: share the chat divider and drop the unsafe run auto-scroll
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: give the labelled divider a border colour and the standard pretty icon
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: pad the agent run below its badges as well as above
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: follow a streaming run's pane without moving the page
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: share the chat's stick-to-bottom mechanics with the agent run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the answer's citations and end a turn's reasoning with the turn
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: read the agent stream through the windmill-chat sdk parser
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show an agent run's thinking instead of falling back to raw json
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop a stream the fold cannot use from claiming the run pane
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: identify an agent run by more than the job id the replay withholds
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: drop the tests and comment lines that were not earning their place
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop a streamed turn's text shifting when a tool call closes it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: flow chat job-backed detail, smooth streaming and answer chrome
* chore: document setFlowPath, drop unused chatIdentity, tighten comments
* fix: read a nested agent tool's call from its own job
* fix: show only the model's arguments on a tool card read from its job
* feat: add attachments to the chat sdk message type
* refactor: read a flow chat message's files from the message, not its run
* fix: keep every job-backed answer when reads finish out of order
* refactor: build flow chat tool cards from the conversation row alone
* feat: keep the model picker's current choice when a flow chat retry replays its run
* fix: export ChatAttachment from the chat sdk entry point
* fix: drop a conversation list for the flow path before setFlowPath
* fix: return the current listing, not old-path rows, after setFlowPath
* fix: treat a listing stale on kind or path as one stale listing
* revert: drop setFlowPath and rebuild the chat when the flow path changes
* fix: refuse a flow chat retry once any turn ran while it read the run
* docs: say where a display message's createdAt comes from
* style: drop the rule between New chat and the conversation list
* feat: attach files to a flow chat message
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: store chat uploads under windmill_uploads and withdraw refused sends cleanly
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse extra files for a single-file input and keep attachments on retry
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clean up partial upload batches, withdraw a stopped send once, free retry payloads
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: list a chat conversation only once its run starts and require files where the flow does
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: carry uploaded attachments on the pending chat user message
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: route rich-composer file paste through the attachment lanes and tighten comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a stopped turn's files for retry and let an explicit media type win
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse chat attachments sent without text and shorten comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve the attachments input from real flow input reads, ignoring loop iteration
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: read flow input references through one parser for the model and attachments controls
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: withdraw an attachment send stopped after its uploads answered
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: discard uploads when a send is stopped as its attachments are announced
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: keep uploadAttachments' doc comment on uploadAttachments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: never delete chat uploads from workspace storage
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: pin that a failed upload aborts the rest of its batch
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>