Repository navigation
Conversation
This was referenced Oct 28, 2026
An OpenRouter account that allows only zero-data-retention endpoints cannot reach a model whose one such endpoint carries a regional surcharge, since OpenRouter passes over that endpoint unless the request names it. Dagu had no way to send OpenRouter's provider routing object. An llm block and each model entry now take provider_order, the upstream providers OpenRouter tries first. The OpenRouter provider sends it as provider.order on every request, so chat, agent, browser, computer, and xlsx steps and model fallbacks all carry it. A model entry's own order replaces the shared one. allow_fallbacks keeps OpenRouter's default, as the account's data policy still filters every fallback endpoint. Only OpenRouter routes among upstream providers, so validation refuses provider_order for any other provider, and a provider named by a value reference is refused once it resolves.
Choosing in an AppKit combo box failed when its list was already open, as a click on the box's own button leaves it. Expanding the box ran AXShowMenu, which opens the text field's context menu, not the list. With the list closed, Escape dismissed that menu and typing still set the box. With the list open, the context menu covered it, and after Escape the box kept the focus but never took the typed option. Expanding now opens a combo box through AXExpanded, or its button when that cannot be set, and leaves an element that is already open as it is. An AppKit pop-up button closes its menu when pressed again, and Chromium's open list box offers no press, so a choice made in either now comes from the open list. UI Automation's Expand skips an element that reports itself expanded too. A scheduler test that read the restart time before the restart function had sent it now waits for the time itself.
A recording made on a row whose field already showed that row's value never entered it, and every row shares the recording, since it is keyed by the instruction and not by the values. A loop's later rows replayed it and left the field as the first row had it. A recording now keeps a salted digest of the value of each variable its instruction names that its actions never type, choose, or find an element by. A run where such a value differs, or a recording that keeps no digest, is not replayed: the model works the act and records afresh, and ai: never fails with the reason. Browser acts follow the same rule. Their cache entries become objects, and a list an older engine kept still reads. A row-text match on an element counts as using the variable. Sessions keep the digests through an export. computer cache show names the variables a pin would leave alone, or says a recording is too old to tell. A conformance poll that failed on one transient run read now reads again; the read race is tracked in dagucloud#3425.
OpenRouter sends 200 as soon as the upstream provider accepts a request, so a provider failure during generation arrives as an error object in the body with no choices. Chat dropped that object and returned a non-retryable "no choices in response" with status 0, so a single transient upstream failure failed the whole step. The error object, at the top level or on the first choice, now becomes an APIError with its code and message. Any server error or rate limit is retried; other codes are not, and a context-length error, found by its message or by OpenRouter's typed error_type, stays one. An error chunk mid-stream ends the stream with the same error. An answer with no choices and no error is retried as transient.
A session command that refused its input after reading the page, such as an element operation on an element with no name, returned without parking the browser. The session's record stayed running under the command's process, which then exited, so the session's watchdog took it for a session whose command died and closed its browser. The next command found the session ended, and what the page held, such as a search's conditions, was lost. A command that attached to the browser and neither parked nor retired it now leaves the session waiting for the next command when it ends.
The write gate answered a blocked page load with a "Not sent" page, which replaced the document the form was filled in on, in the main frame and in frames alike. A screenshot meant to show the person what waits for approval showed only that page, and the operation run again with --allow-write found no form to submit, so the approved write could not be sent. A blocked load a page started, by a form or a link, now fails with Aborted. The browser cancels it without committing an error page, so the frame keeps its document; Chrome does this for a form posted in the main frame, into a frameset frame, and inside an iframe. A load counts as the page's when it is not a GET, names a Referer, or was sent under the no-referrer policy. A GET the browser loads on its own, as a goto does, still gets the "Not sent" page: the browser runtime's goto waits for a page that a cancelled load never brings. A rehearsing session's result now names a write it blocked in a warning, since the page no longer shows it, and the do help says a held form is sent with the values it holds when its write is allowed.
A fixed text or selector check, in expect or when, read only the top
document. A site built of frames keeps everything a person reads in its
frames, and its top document is a frameset, whose body has no text, so
expect {text} and when {selector} never held there: an expect waited out
its whole window and failed with "the page text does not contain", and
a sign-in guarded by a selector was always skipped.
Both checks now read the page's document and the documents of the frames
it shows, nested ones too. A frame hidden by its size, display, or
visibility is left out, a body that is not rendered gives no text, and
a frame from another site cannot be read.
The write gate reports the start of the answer to each write it sends, which the browser hands over as bytes. Those bytes were read as UTF-8 whatever the answer's charset, so a site that answers in Shift_JIS or EUC-JP, as many older Japanese sites do, showed only replacement characters and stray letters where its text was. The bytes are now decoded from the charset the answer's content type names. Secrets are hidden in the bytes as sent and again in the decoded text, so one sent in another charset than the one named stays hidden. An answer with no charset, or one not known, reads as before.
A form posted into a frame, as sites built of frames search and open a record, keeps the frame's old document until the answer has loaded. Waiting for what an action started watched only the top document's requests and changes, so it saw nothing of that load and ended at once. The next operation read the frame as it was before the action, and a session command showed the page before the search and reported the write it sent only with the next command. The browser now reports which frames of its pages are loading a document, cross-origin frames under them included, and the wait goes on while any frame of any of its pages loads. That also covers a press that opens a window in front, as a "searching" notice does, and the wait reads the page in front each time, since such a window closes once the answer loads. A navigation can begin a moment after the action that starts it returns, so the page is now watched for the quiet period after every action; before, the period could run out while the model was still choosing the element.
A goto to an address answered with a download or with no content waited out the whole operation timeout and then failed with "Navigation start timed out", though the download was saved. The browser runtime's goto ignores the error Page.navigate reports and waits for a navigation event that a load without a document never sends, and it took the address for the page's while the page stayed where it was. Goto now loads the address over a DevTools connection of its own to the page. It returns once the new document is parsed, the browser's error page included; at once when the site answered without a document, a download first seen to begin so the goto reports it; and it fails when the browser cancelled the load before any answer, as when another load replaced it or the write gate held it. With goto no longer waiting on a cancelled load, the write gate cancels every page load it holds in place, whoever started it, instead of answering some with a "Not sent" page. Fixes dagucloud#3433
On a site built of framesets, wait {selector} never held: the runtime's
waitForSelector looks for a CSS selector in the top document only, and
the element was on screen in a frame. An upload without into likewise
counted file inputs in the top document only, so the only file input of
a frameset page was reported missing.
A CSS selector wait now polls the frame-aware selector check until it
matches or the timeout ends, with the same error as before, and its
reads end with the wait's own timeout. A selector that hops into frames
with >>, or an XPath, still goes to the runtime as written. The selector
check also matches inside open shadow roots, which the runtime's wait
pierced, so wait and expect/when {selector} agree. An upload without
into finds the only file input of the page and the frames it shows. A
frame from another origin cannot be read and is left out, as the step
reference now says.
Forms on older sites are laid out in tables that label a field with the cell before it, not with a label element. Such a control has no accessible name, so the session outline showed a bare select or textbox, and an element operation refused it as having no name a step could find it by again; every field of such a form needed a model call. A control the tree leaves unnamed now takes the label cell of its table row: the nearest cell before it that has text and holds no control or button, in data and layout tables alike, trying the enclosing row when the nearest has none. Controls of one role that one cell labels, as the two fields of a date range, are numbered in order. A control no row labels takes its name attribute, or its id, read through frames of the page's origin and open shadow roots. The outline, element operations, and replay identification all read these names, so they agree. A target recorded with no name is compared among the names the tree itself gives, so recordings made before rows named controls still replay.
A search on a site built of frames can open a small "searching" window and post into a frame of the page, whose answer closes the window once it loads. The browser shows the window in front, and every operation read the page in front, so when the answer took longer than the five seconds an action waits for its page to settle, the operations after the search read the window: an expect failed on "communicating with the server", an extract read nothing, and an act found no element. The frame load watcher now also tells which page opened each window and when each page's frames began and stopped loading. A window the last action opened while making its opener load a document is held: operations work on the opener, and the action waits, up to its own timeout, until the window closes or stays open half a second past the load. A held window stays held through later actions while its opener loads, and once let go it is never held again. A window still open then, such as a list of regions to tick and register, is the page operations work on, as is one opened without a load in its opener. Acts and observes name the page they work on rather than leave it to the runtime's page in front.
A browser step keeps a record of its browser under an ID made from the DAG run and the step name, and closes the browser a record under its ID names when it starts, as one an earlier attempt left open. The items of a foreach run their body steps under the same DAG run and step names, so an item starting while another item's browser was open closed that browser, and the other item failed with "stagehand CDP connection closed" at its next operation. A body step's environment now tells which foreach item, nested items included, it runs in, and the browser record ID includes that item. An item's own retries and resumes still find its record.
yohamta0
force-pushed
the
stack/01-g1
branch
2 times, most recently
from
October 31, 2026 09:21
4767ba6 to
ac51000
Compare
Name every tier and event kind in switches, bound the histogram bin conversion, and drop an unused parameter. The crash test now releases the handles of the dropped recorder, as the operating system does for a crashed process, so Windows can remove the temporary directory.
Give each step its own store directory, end a cut line before the next append, keep the outage failure count across a restart, close backfill files on every return and reject sub-millisecond intervals, cover every duration with a histogram bin, and report a bad events cursor as ErrInvalidCursor. Spec 083 now states the clock rollback rule, the outage rule across restarts, and the precision of the streak.
Backfill kept an hour and a day that were still open at End in memory and never wrote them. Without a raw tier the reader cannot rebuild them, so the hour and day tiers lost the end of the history, for good once the finer files were past retention. Backfill now writes the open buckets, as Recorder.Close does. Its stopped event also carries the failed count of an open outage, so that a Recorder opened afterwards continues the count instead of restarting from the last repeated failed event. Record clamped a backward sample only to the start of the open minute, so a stored time could still be earlier than the previous sample and the raw buckets came out of order. A sample before the newest one now takes the newest time. The spec states this rule and the failed count of repeated failed and outage stopped events.
Two steps whose names differ only in case got directories that a case-insensitive file system (APFS, NTFS) treats as one, so both recorders wrote and pruned the same files. A kept directory name is now lower case, and any name with an upper-case letter gets the hash. The streak of an availability query loaded every sample of a raw hour before it scanned them, so its memory grew with the sample rate. It now keeps only the last failure and the count after it. The package doc gave an old histogram range, and spec 083 claimed a raw-tier disk bound that a fast or failing step could exceed; both now state the actual limits.
A recorder opened on an existing store forgot its newest sample. A minute that a flush wrote before a crash, and that had not ended when the store opened again, was never aggregated, so every tier left it out. A clock that stepped back across the restart stored samples out of time order. The recorder now restores the newest stored sample, bounds the next one by it, and opens its minute again. A crash writes no stopped event, so the outage count fell back to the last failed event, which can be an hour old. The count now adds the failed samples that the raw tier holds after that event. A stopped event is no earlier than the newest sample, so a clean restart counts none twice. The 24 hours availability window started at the next whole minute and dropped up to 59 seconds of samples. It now starts 24 hours before now, to the second.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
All PRs of the stack
Final render
Screenshots of the result once the whole stack is merged (demo data). PRs 5 to 27 build this UI step by step; the backend PRs 1 to 4 and the e2e PRs 28 to 30 support it.
Summary
This PR adds
specs/083-continuous-runs.mdand a new package,internal/samples. The package stores the executions of a step that repeats without a limit as samples. It rolls them up into minute, hour and day tiers and answers queries for samples, events and availability.Why
A step that repeats every second for years cannot write a full status record each time. It needs a bounded store and a defined contract. This PR is the base of the stack: PR 3 writes to the store through the runner, and PR 4 exposes it through the REST API.
Changes
specs/083-continuous-runs.md: defines continuous steps, samples, tiers, retention, events, bounds and the API queries.specs/README.mdlists it.internal/samples/contract.go)Sample,Tier,Bucket,Series,Event,Availability, and theRecorderandReaderinterfaces.Dir(attemptDir, stepName)gives the store directory of one step.files.go,doc.go)raw/by hour,minute/by day,hour/by month,day/by year, andevents.jsonl.agg.go)aggholds count, failures, min, max, sum and a log histogram with 16 bins per octave. Two aggregates add bin by bin, so percentiles stay within about 2.2%.recorder.go,rollup.go,events.go)OpenRecorderrepairs the files, then accepts samples.Record,FlushandCloseclose each minute, hour and day. Roll-ups are idempotent.pruneremoves a file when its tier retention has passed. Thetrackerturns outages into events.reader.go)OpenReadergivesSamples,Events(cursor paging) andAvailability(windows and streak). It builds the open bucket from the finer tier.backfill.go:Backfillwrites the old tiers in bulk, used for synthetic data.How to review
specs/083-continuous-runs.md, then the package comment ininternal/samples/doc.go.contract.goandagg.go.recorder.goandrollup.go, thenreader.go.Pay most attention to these places:
repair,putBuilt): a crash must lose at most one minute and never double-count.agg.goandReader.Samples(the open bucket).Tests
internal/samples/store_test.go: percentile accuracy, merge equals whole, idempotent roll-up, retention, crash repair, events, outage across restart, empty store, bounds.internal/samples/reader_test.go: tiered samples, availability, old failure streak.internal/samples/helpers_test.go: shared helpers.Screenshots
No visible UI change in this PR.
The Continuous tab that shows this data appears in a later UI PR of the stack.