Repository navigation
Wave 1: merge the 78 checked fix PRs into main - #1256
Merged
Merged
Conversation
The task store records a run a budget, time or step ceiling ended as completed, with the cause beside it. The overnight command printed "overnight completed" and exited 0 for it, so a script calling it took unfinished work for a success. It now prints "stopped (<cause>)" with the plain reason line and exits 2; 0 stays for a run the agent finished on its own. ATO-1323
…busy engine `models update` retired an idle Core after every failure. When the refusal was "Another engine operation is running", nothing had been installed or started by the command, and the lock's holder (Studio putting Core in place, a model load) may have just started a Core it was about to use. Retiring it then made that caller's next request fail and start Core again. The desktop's retry loop for a held lock (#1105) makes this window come up more often. A busy-lock refusal now prints its line and returns 1 without touching Core. Every other failure still retires an idle Core as before. ATO-1348
A compact prompt mode this build does not know was read as auto, and the next whole-file write (any settings toggle) put auto over the value the newer build wrote. The key now joins the other set-aside enum settings: read as auto with one notice, kept on disk, refused when typed in config set.
… missing path The missing-path hint searched the working folder and the folders above it under one shared entry budget. A crowded parent (the system temp folder, ~20,000 entries) used it up, so a match below the working folder was never reached and the answer said the quick search found nothing. Roots above the session are now marked outer and searched after the near ones, under their own small budget and a per-folder cap; their matches are listed after the near ones. Fixes ATO-1333.
…l alias A fine-tune with the Qwen or Nemotron ChatML template served under a neutral alias fell to plain-instruct and lost its reasoning channel, because the think-tags profile required a family name in the alias or GGUF file name. The template alone now resolves qwen-think when it has ChatML markers, a <think>/</think> pair, a thinking control, and a generation prompt that opens the assistant turn with <think>. GLM and Harmony templates that only mention <think> stay plain, as does a ChatML template that only strips old think blocks from history. The "model profile resolved" log line now says what matched: template, name or fallback. ATO-1294
…p (ATO-1365) nameEscape is not injective: mcp.srv.foo__bar and mcp.srv.foo.bar both escape to mcp__srv__foo__bar, and nameUnescape read it back as the dotted sibling, so a call to the first tool ran the second. The OpenAI adapter now assigns each descriptor a distinct wire name (names that round-trip keep their plain escape; a colliding name gets a deterministic _2/_3 suffix) and exposes a wire-to-canonical map built in the same memoized pass. Step parsing feeds it into toolCallsToBatch and the content-array recovery paths; nameUnescape stays the fallback for names the request did not offer.
trace replay handed the raw default catalog to the prefix builder. That catalog now holds tool.search, which the runtime mounts only in tool search mode, so every replay rendered the search-mode wording and a session recorded with the mode off reported drift on every step. Traces do not record the mode, so the replay now builds the prefix with and without it (applyToolSearch, as the runtime does) and a recorded hash is clean when it matches either, the same way it already treats transport and the compact scaffold. A new toolSearch option pins it; matchedToolSearch reports which variant matched. Refs ATO-1335
…ckout (ATO-1296) The catalog carried two descriptors for each of the three git write tools. The full prompt showed both copies, and getToolDescriptorByName returned the second, so tool.view and error auto-expansion described os.git.commit without paths although the tool accepts it. Keep the copy that matches the implementation and its JSON Schema (commit with paths, push with setUpstream, checkout in schema order), fold in the accurate bits of the dropped copies (Remote sync note, branches only, the commit example), and drop the duplicate ledger from the conformance test so descriptor names must now be unique.
The nearest existing folder of the missing path and a protected folder that holds it are where the file was expected, so they keep the full budget. Only folders above them are outer.
…hanges nothing - model-params: the Claude id rule is anchored at the start of the id (after the router prefix, with an optional `us.` / `anthropic.` Bedrock prefix), so a fine-tune that only names Claude, such as `Qwen3-8B-Claude-Sonnet-5-Distill`, keeps its temperature. - openai-provider: a refused-temperature 400 for a model already recorded as taking none, on a body that carried no temperature, gets no resend, since the same body would go out again. Linear: ATO-1397
The browser bypass check read the text of print-only calls as if it ran,
so python print("open https://..."), console.log(...), sys.stdout.write
and process.stdout.write were refused at every approval level (ATO-1349,
ATO-1353). String literals passed straight to these calls are now emptied
before the line rules run; strings inside nested calls, f-string and
template interpolations, and any output piped on to a runner are still
read. echo/printf with several quoted arguments are exempt too.
…ed shell -c lines A command the shell guard deems safe (echo, ls) never reached the approval gate, so a saved deny rule on it did not apply. os.shell.run now asks the gate's rule decision for such a command and sends a deny to the gate, which refuses it before any level, grant or prompt. Ask and allow rules keep their current semantics. shellRuleSegments now also lists the commands a nested bash/sh/zsh -c line runs (bundled flags like -lc, -ec, recursively, bounded depth), so a deny rule on rm stops bash -c "rm -rf tmp". The nested-shell option scan is shared with the guard's dangerous layer. Refs ATO-1332
…nder The approval level was set per process, not per task. An overnight run that denied actions needing approval, once interrupted, could be picked up by another process's scheduler at a higher level (and run what it had denied) or at a lower one (and wait for a person who is not there). The overnight command now stores its level on the task row, in a new nullable approval_level column added the same way as the budget columns (older binaries ignore it). The task runner leaves a row with a level for a process running at that level, logs it once, and the drain loop does not count it as a retry. ATO-1324
- The composer's $ readout now includes Fusion workers: a worker's throwaway session forwards its calls to the session that delegated, priced by the same rules (unpriced stays unpriced, local stays local). Escalation and judge calls, which go straight to the provider, are counted on the delegating session too. (ATO-1388) - A task re-run on the escalation model keeps the local attempt's usage on its row, so the status header and the checkpoint no longer price the re-run a second time on top of its escalation spend. (ATO-1319) - A split's subtask finds its root through the task tree's parent links, so its spend and side effects reach the run checkpoint and the resume budget. (ATO-1320) - The overnight doc and `task create --max-cost-usd` help say that Fusion workers are not in the money cap and point to llm.runMode.fusion.turnBudgetUsd. (ATO-1315)
blankPrintedLiterals, the print-call pattern, the literal scanner and the check for output that could be run now live in printed-literals.ts next to browser-bypass.ts, which imports the one function it needs. No change in behaviour.
The reasoning rule only knew o1-o9 and gpt-5, so gpt-6 ids (Sol, Terra, Luna, Astra) went out with temperature and max_tokens, the two fields OpenAI's reasoning models refuse. Every GPT generation from 5 on now gets no temperature and max_completion_tokens.
…in the window - openai-native-messages: under `reasoningRequired` every assistant tool-call message carries `reasoning_content`, an empty string where the call has none. Before, the empty field was added only when some call carried reasoning, so the first step of a follow-up turn sent the answered calls without it. A new `reasoningBudget` option caps what is handed back, newest calls first. - openai-build-body: the reasoning handed back is held to the room the cloud window has left after the same messages without reasoning, the tools, the reply reserve and the safety margin. The cloud prompt does not count reasoning, so a long thinking turn failed the capacity check instead of sending less of it. - Tests: first step of a follow-up turn, the budget, a tight window, and one path from session through buildPrompt to the body sent to api.deepseek.com. Linear: ATO-1397
A turn the spend ceiling ended still fired the session namer, a paid side call (up to 512 output tokens) that the budget never counted. The promise is "stop at the cap", so the namer is now skipped when the turn stopped for money (its own budget or the provider credit). The session stays unnamed until a later turn answers within its budget. ATO-1350, ATO-1354
GET /api/sessions/{id} now adds done_line to every assistant_tool_call
row, built with describeToolCall from the stored name and args. While a
turn runs the desktop labels a step with tool_progress.done_line, but
that line was never kept, so a reopened chat fell back to its own table
and labelled MCP, Composio and newer tools differently. Nothing new is
stored; an older desktop ignores the field.
Refs ATO-1382
- A local worker the person stopped, or one swapped for the escalation model, now marks the local leg as possibly wedged, so the next local worker asks the server first instead of waiting out a queue budget. The cut is read before the re-run resets its flags. (ATO-1317) - The re-run on the escalation model reports its tool calls as they start, so a crash mid-re-run leaves its side effects in the checkpoint and the task is not offered as safe to repeat. (ATO-1316) - The fan-out pool starts nothing more once the turn is stopped, also when no budget cap is set. (ATO-1334)
The live catalog's bonuses named single versions (gpt-5., qwen3.7, qwen3.6, kimi-k2.6), so a new release such as GPT-6, Qwen 3.8/4 or Kimi K3 got no bonus and sat below the model it replaces. Each family bonus now covers its generation and every later one, ties go to the newer row by OpenRouter's created date, and :batch variants leave the picker. Scoring moves to its own file with tests.
… server rejects
LM Studio and llama.cpp refuse the whole chat request when one tool's
JSON Schema holds a boolean subschema ("evidence": true), answering
'JSON schema error at #/...: schema must be an object' (ATO-1399).
- normalize boolean subschemas in every OpenAI-compatible tools array
(true -> {}, false -> dropped property/branch or { not: {} });
additionalProperties and friends keep their boolean values, and tools
with nothing to rewrite stay byte-identical
- treat a schema error as non-retryable instead of three blind 5xx tries
- when the error's JSON pointer names exactly one tool, resend once
without it (unary and stream) and log a warning naming the tool
- give the leftover case its own user-facing sentence
On a 16K window the reply reserve is 4096 tokens. A local reasoning model thinks inside that reserve, so a reasoningBudgetTokens above about 3000 used it up and the tool call was cut. The reserve now holds at least the bounded reasoning budget plus 1024 tokens for the tool call, still capped by completionMaxTokens and half the window. It applies only when the profile reasons and thinking is not off. At load time the runtime warns when the budget still does not fit. Automatic compaction that finds no cut on a model without prefix reuse now says so once per session and window, like the existing "window too small" notice, instead of trimming turns silently. ATO-1295
overnight --notify telegram queues its report in memory until its own Telegram channel is up. When the desktop app or the TUI holds telegram.lock the channel never comes up in the overnight process, which then exits and the report is gone without a word. Before exiting, overnight now gives a still-connecting channel up to 15 s, and if the report is still waiting prints why it was not sent (naming the other agent holding Telegram) and where to read the result. Sending through the lock holder is left as a follow-up. ATO-1358
The agent sends no reasoning effort unless a Fusion worker sets one, so Fable 5.2 on OpenRouter ran at the API default, high. On a measured task that cost about four times medium for the same tests passed. A turn that asks for no effort now sends medium for Fable 5.2 and later on OpenRouter; an explicit effort, extraBody or model params still win.
With mixLocalModels two local servers can serve one fan-out, but the health mark set after a cut-off worker was shared, and the only probe asked the main server. A wedge on one server could fail a worker sent to the other, and an answer from the main server cleared the other's mark. Marks are now filed under the leg's provider id, and a leg is asked only through its own server's probe. The runtime has a probe for the main local server only, so a second server's leg is not checked rather than checked against the wrong one. (ATO-1318)
Every write of a session (each step's checkpoint, every turn's end) serialized the whole transcript into one JSON string, even the part a compaction had already summarized. On long sessions and overnight runs that string grows without bound, and so does the peak memory of each write. Turns covered by a compaction checkpoint now move once into a new session_turn_archive table, one row per turn. The row's payload keeps only the turns after them, plus an archivedTurns marker with the count and the first user message (for the session list). SessionStore.load, listRecent and listByWorkingDir put the archived turns back in front, so the in-memory transcript and every absolute index into it (coveredThrough, macroTurnStarts, pack start) are unchanged. - Archive rows are append-only and written in the same transaction as the payload. A write whose transcript no longer starts with the archived turns (an older copy, a replaced history) drops the archive and stores the transcript in full, so a load always returns what was saved. - Rows written before this change load as before and are migrated on their next write. Sessions without a checkpoint are stored exactly as before. - listChatLines and the summary page read across the archive. - delete, the retention pass and the desktop importer handle the archive. If the table cannot be created (read-only file, lock held), the store keeps storing transcripts in full. Refs ATO-1271
The compaction summary call asks for low reasoning effort, but the field was only sent to OpenRouter and OpenAI-compatible providers. Gemini and AI/ML API got nothing, so a thinking model there could reason past the 4096-token headroom and the summary fell back to the crude extractive one. reasoning_effort is now sent on Gemini for its thinking models (2.5 and 3.x, not image, speech, live or embedding variants) and on AI/ML API for OpenAI reasoning models (o1, o3, o4, gpt-5). Other models on those providers keep the body as before, since the field is not documented for them. ATO-1359
Each /goal round is an ordinary turn, and each got the full task money cap, while the result's spendUsd covered only the last round. A goal under an overnight budget could spend the cap once per round, and the task's spend record undercounted it. The goal controller now gives each round what the earlier ones left of one cap (the run's own, else agent.task.maxCostUsd), stops with budget_limited when it is used up, and reports the rounds' spend summed. A cap of 0 still means none. ATO-1288
…t as needs you The Telegram report's meta line showed this run's spend only, while the cap covers every attempt of the task. It now shows the task's total as "spent ~$X of $Y" when the task has a cap, and this run's spend only when no total is recorded. A task the runner blocks with budget_spent: or time_spent: never reached a turn, so its report was the bare error line. It now gets the usual card with the budget or time line under Needs you, the error kept under Failed. ATO-1288
… the catalog budget
…t the catalog budget
This was referenced Nov 12, 2026
Closed
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.
Merges the 78 open fix PRs based on main in one go, with the conflicts between them already resolved.
PRs included: #1117 #1118 #1122 #1123 #1124 #1125 #1130 #1131 #1132 #1133 #1134 #1135 #1136 #1137 #1138 #1139 #1140 #1141 #1147 #1148 #1149 #1151 #1153 #1154 #1155 #1156 #1162 #1163 #1164 #1170 #1171 #1172 #1173 #1174 #1175 #1176 #1178 #1184 #1186 #1190 #1192 #1193 #1194 #1195 #1196 #1202 #1205 #1207 #1208 #1209 #1210 #1211 #1212 #1213 #1214 #1215 #1216 #1217 #1218 #1221 #1222 #1223 #1224 #1225 #1227 #1228 #1233 #1234 #1235 #1237 #1238 #1241 #1244 #1248 #1249 #1251 #1252 #1253
Checked on the second Mac on 12.11.2026 on this exact tree: build, lint, test types and vitest (19330 passed, 4 skipped). Merge with a merge commit (not squash) so each PR above is marked merged.