# Changelog

## v1.0.60

- Isolated visitor sessions with scoped tools now available, keeping private activity out of shared thread lists
- Live view now keeps page scripts and fetched content intact
- Bug fixes and stability improvements

## v1.0.60

- **Restricted visitors stay out of your thread list.** An app can now open a one-off, locked-down conversation of its own — its own tools, its own folder, no access to anything else. Those conversations no longer clutter the thread list in the web app or the API, for either an owner key or an app key. Opening one directly still works; hiding it from the list is not a lock.
- **Apps can hand a visitor exactly the tools it should have.** A visitor is given a named set of tools and nothing more, and every message it sends is stamped with where it came from, so its work can be traced back to the run that asked for it.
- **Live View keeps pages whole.** Pages fetched for review keep their scripts and markup intact instead of losing pieces while being prepared for viewing.

## v1.0.59

- **Interface is now Live View.** Controls, accessibility labels and captures use the new name.
- **App authentication survives delivery.** Application Authorization and X-API-Key headers reach the app while gateway credentials remain filtered. Same-origin native login forms retain their origin, fixing Sonarr and Radarr sign-in.
- **Relayed apps can retain their actual server address.** The optional upstreamHost setting supports qBittorrent Host and reverse-proxy checks while retaining the registered loopback connection, browser identity and relay job. Karl confirmed live qBittorrent sign-in; theater still reports a post-login blank-content issue involving cross-origin frame access, so full embedded UI and refresh verification remain open.

## v1.0.58

- **Preview is now Interface.** New views default to Fit while saved viewport choices remain intact. The connection dot sits before Back/Forward, Route sits before Select/Comment, and responsive spacing lets the page picker shrink to 64px before wrapping. Panel actions are Tasks, Includes and Revert, with Tasks before Includes.
- **Navigate inside Interface.** Back and Forward revisit addresses within the selected page and branch without navigating the outer browser tab. Revisiting reloads the page; unsaved form state is not restored. Branch appears only when multiple usable branches exist; a sole branch is selected automatically.
- **Clear empty states and an Escape shortcut.** Unconfigured panes show “Nothing is currently set up.” centered with controls disabled, then recover when setup appears. Escape in Comment switches to Select and clears unfinished text and drawing.
- **Cumulus development previews show current UI source.** Refreshing the development Interface picks up source edits instead of serving stale production assets. Backend changes still require restarting that development preview.
- **File-editing instructions match the available tools.** Threads can use their harness's structured editor, including apply_patch, without stopping to ask for permission. The Read/Edit handshake still applies where those tools exist; shell-script patching remains prohibited.

## v1.0.57

Patch release. A co-thread keeps the settings it inherits, works in the right project folder, and has its own Preview pages.

- **Changing one setting on a co-thread no longer wipes every inherited one.** A thread's settings are now merged field by field with its base thread's: what you set on the co-thread wins, and everything you did not mention still comes from the master. Previously the nearest settings file was taken whole, so picking a model or an effort level on a co-thread silently cut it off from the master's project folder, memory and included files — which is why Preview said the thread had no worktree. The measured consequence was worse than Preview: affected co-threads had been running their turns against an empty scaffolded folder instead of the real repository, leaving their own checkouts unused. Schedules and co-thread membership deliberately never inherit.
- **A new co-thread starts with its own copy of the master's pages.** Preview inside a co-thread is no longer empty: the master's pages are copied once when the co-thread is created, pointed at the co-thread's own checkout and branch, and are entirely its own from then on — it adds, edits and stops them freely, with no lookup back to the master. Nothing is launched by creating it, no port is shared, and each page gets its own browser origin, so preview storage and cookies stay separate. Co-threads created before this release start empty and register their own pages.
- **One folder, one builder.** Registering a preview that runs a build or dev server is refused when another thread is already running one in that same folder, naming the thread and page that holds it. A master previewing a co-thread's branch runs inside that co-thread's checkout, and two builds writing one tree corrupt each other silently. Reading a folder is not building it, and stopping the build releases it immediately.

## v1.0.56

Patch release. Preview pages load in a fraction of a second instead of tens of seconds, and a single failed check can no longer sever live reload or kill a healthy dev server.

- **A preview is verified once per page, not once per file.** Delivery re-proved the preview before every file it served — five git subprocesses, a walk of every process on the machine, a source re-hash and a thread-config read, each time — and a page is hundreds of files. One verification per preview origin now answers for `liveMs` (1 s) and is shared by concurrent callers; the registry shares an in-flight check and skips one younger than that window. Measured on the real code paths with `scripts/measure-preview-delivery.mjs`: a 106-file dev-server page load falls from 7,131 ms to 92 ms, and a static branch preview from 742 ms to 54 ms.
- **The expensive half of the check runs at the poll cadence.** Git worktree resolution and `jobOwnsPort` run every `deepMs` (default 5 s), or whenever a record is not `ready`, so a switched branch or a stolen port is noticed exactly as quickly as before. A shallow negative escalates to the full proof before anything acts on it.
- **Only a settled fact may end a preview.** Worktree gone or moved, static root gone, git's own inventory rejecting the branch, or a build job exiting non-zero. A check that could not finish keeps a ready preview serving for `graceMs` (30 s), then degrades to `disconnected` — and never cancels the job. That path had terminated a healthy dev server over one failed git read. An explicit `start()` replaces a preview that stopped answering.
- **A failed expiry tick no longer severs live reload.** Delivery skips the tick instead of destroying every socket on the origin, which had cut the app's own live-reload channel and made a backgrounded tab reload the whole page.
- **A changed source revision colours the Reload button and does nothing else.** No frame reload, no lost page state, and no block on Comment mode. When the app reloads itself, a draft in progress is recovered rather than discarded — the typed note survives, stale markup and anchors are dropped, and marking continues without a forced Cancel.

## v1.0.55

- **Preview servers no longer suppress stall recovery.** Long-running Preview services are excluded from the pending-work gate. Finite build jobs and scheduled continuations still prevent redundant nudges.
- **Interrupted Codex turns recover after reload.** Partial output cannot acknowledge a scheduled continuation, even when a terminated process exits with code zero. Shutdown records unfinished threads before terminating their processes, allowing startup to retry the retained schedule or resume the interrupted thread.
- **Comment directly on Preview.** Select uses the app; Comment selects an element or draws red annotations and attaches a capture with page, branch, viewport and target context. Sent feedback shows the comment and a compact annotated image after refresh, with the original capture available to reopen.
- **Thread ordering reflects family activity.** Mainline threads move up when any registered co-thread has newer activity, while co-threads remain alphabetically nested.

## v1.0.54

- **The reviewed thread rail is now on main.** Collapse navigation into an avatar rail with co-thread cards and shared activity indicators. The Cumulus menu lives in the rail; on mobile, a hamburger beside the conversation title opens the full drawer. A lone conversation no longer shows a close button.
- **Branches follow the chosen page or file.** Preview lists only branches that can serve that selection. Saved choices remain per page; an unavailable choice shows an explanation without silently opening another branch.
- **Catch missing Preview launch files before starting.** Branch availability now checks declared launch sources as well as the page itself, so an old checkout missing its launcher is rejected before a job starts.

## v1.0.53

- **Choose a page or file, then its branch.** Preview shows the project-relative source path and a separate branch picker, initially selecting main (or master when main is absent). Explicit choices are remembered. The selected branch's prepared worktree supplies the page; missing files and unprepared branches show an explanation without silently switching sources.
- **Open addresses when needed.** Route entry moves into the secondary Open address popover. Preview remembers addresses and keeps separate browser state for each page and branch, while preserving query strings, ordinary navigation, reload and new-tab behavior.
- **Keep branch selection consistent.** Late startup responses cannot replace a newer selection, and delivery refuses a worktree whose checked-out branch has changed.

## v1.0.52

- **Review actual project files in Preview.** Reuse persistent Preview targets serving the repository, its ordinary build output, or its development server. Static HTML and image entry routes work without an index.html. Retiring a target preserves project files and previous exports. The snapshot review API and upload_media review option are removed; stale review arguments are rejected before upload.
- **Fit the preview to its pane.** Viewport presets now include Fit, and Width and Height each accept Fit or a pixel size. Fixed dimensions automatically scale down to the available space. The manual Scale selector is removed; resizing preserves the page and its form state.
- **Dismiss viewport settings from the app.** Clicking or tapping inside the preview closes the settings popover, including Safari mobile taps that do not move focus. Inputs, native dropdowns, repeated toggling, and Escape focus restoration are preserved.

## v1.0.51

- **Preview beside the conversation.** Open registered project apps on authenticated, isolated origins, with a target picker, route navigation, viewport and scale controls, and a resizable chat divider. Mobile panels start on chat; Preview opens when requested.
- **Reusable project previews.** Saved launch recipes start or reconnect to the app when Preview opens, using automatic ports and isolated development data. Stable browser origins preserve local storage across restarts.
- **Preview tools honor thread allowlists.** All four lifecycle tools are included in the permission inventory, so restricted Claude threads deny them unless explicitly allowed.
- **Publish reviews to Preview and public media together.** `upload_media` accepts a named `review` for images or self-contained HTML asset folders. Images get a gallery page; HTML retains relative assets. Republishing updates the same picker target without opening Preview or replacing the default app. Partial registration failures report the available public link and the error.
- **Scheduled wakes survive queues and reloads.** One-shot schedules remain on disk until their turn saves a non-interrupted response. Failed or interrupted delivery retries, and finishing an old occurrence cannot remove a replacement. Delivery is at least once: a crash before acknowledgment can replay effects.

## v1.0.50

- Malformed tables recover automatically when their missing column keys are unambiguous.
- Blocks that cannot render show their original content instead of only an error.
- Streaming blocks stay as placeholders until the response finishes; no extra model calls.

## v1.0.49

- Bug fixes and stability improvements

## v1.0.49

Patch release. The **Refresh** button in `/tasks` works on your other projects, not just this one.

- **Refresh no longer stops with "Author identity unknown".** Before it rewrites your task list it commits the file, so you can always undo — and that commit was failing on any project that had never been told a name and email to sign commits with. Since this machine has no name and email set anywhere by default, that was every project except cumulus itself, which happens to have its own. The button therefore worked exactly where it was built and nowhere else.
- **A project with no signature set gets one, just for that commit.** The snapshot is signed `cumulus` and nothing is written into your project's git settings. Where you _have_ set a name and email, the snapshot is still signed by you, exactly as before.

## v1.0.48

Patch release. A Codex turn now shows what it is thinking, not only what it is running.

- **The problem.** A Codex thread could sit for minutes showing nothing at all and look stalled when it was working normally. Four turns on `@novaburn` ran for 3, 8, 4 and 2 minutes, each finishing cleanly with 9 to 16 tool calls — but most of that time was spent thinking between those calls, and the Codex CLI reports nothing during thinking unless it is asked to.
- **The fix.** Every Codex turn now asks the CLI for its reasoning summaries, which appear in the activity chip as they arrive. The gaps between tool calls stop being silent.
- **What was already working, and stays unchanged.** Tool activity was never held back — each command and each tool call was already showing live as it started. Only the thinking was missing, and only from the Codex path; Claude threads have always shown theirs.
- **The gateway's own background calls are deliberately left alone.** The stall classifier and the Refresh worker have no screen to look alive on, so they do not pay for summaries nobody reads.
- **Not addressed here:** long silences on HuggingFace and Vultr models. Those run through a different path and have a different cause; this release does not change them.

## v1.0.47

Minor release. OpenAI models can run through the Codex CLI, not only the API.

- **A fourth provider kind, `codex-cli`.** A model entry with `"provider": "codex-cli"` runs each turn as `codex exec`, the way `claude-cli` runs the Claude CLI. It authenticates with the ChatGPT subscription via `codex login`, so there is no API key to configure. In Settings it appears as its own card, "OpenAI Codex CLI", with no credential row.
- **Same prompt, same tools.** A Codex turn gets exactly the prompt a Claude turn gets, on stdin, and the same cumulus MCP servers (history, memory, agents, jobs, schedules), translated into Codex's own configuration syntax. The thread's `effort` maps onto Codex's reasoning effort. Text arrives per completed message rather than per token; tool activity shows in between, so a long turn does not read as a stall.
- **The gateway's own calls too.** A `codex-cli` model chosen for the stall classifier or the worker runs through Codex in a read-only sandbox, rather than falling through to an HTTP provider under someone else's key.
- **What is refused, loudly.** A thread that restricts tools (`allowedTools`/`disallowedTools`) is refused on this path rather than run unrestricted, because Codex has no equivalent flag. A missing `codex` binary is reported with the install command.
- **Sessions are not resumed.** Codex runs ephemeral; the gateway's retrieved-context prompt is the conversation memory, as it is for every other provider (ADR 0007).

## v1.0.46

Patch release. One damaged line in a thread's history no longer makes the whole thread unusable.

- **What happened.** After a hard crash on Sep 3 the machine had recorded the new length of `ordimor-farming`'s history file but not the bytes of its last message, leaving a run of NULs where that message should be. The next message written landed on the same line. From then on every attempt to talk to the thread, and every stall check, failed while reading the history back with "Unexpected token … is not valid JSON". The gateway did not cause the damage, but it turned one lost message into a dead thread.
- **The fix.** History is read tolerantly. A message written straight after a NUL-filled torn record is recovered by dropping the NULs; a line that cannot be read at all is skipped. Either case is reported in the journal, naming the file, so the loss is visible rather than silent. The thread keeps every message that can be read and stays writable.
- **What it does not do.** The file is never rewritten by the reader. A message whose bytes never reached the disk is gone; the log says so.
- **The live file was repaired by hand** with a backup kept beside it, so the co-thread works again before this release is installed. The release is what stops it happening to the next thread.

## v1.0.45

Minor release. Threads and co-threads can be renamed.

- **Rename from the sidebar.** Right-click a thread and choose "Rename thread…". Everything the thread has moves with it: history, memory, settings, sessions, background jobs, schedules, and its place in every open tab. There is also an endpoint, `POST /api/thread/:name/rename` with `{ "name": "…" }`.
- **A co-thread keeps its master.** The dialog holds the master's prefix fixed and lets you change only the label, because a co-thread is recognised by that prefix plus its registration; a name that dropped it would lose the shared memory and its private checkout. Its worktree directory and branch move with it.
- **Renaming a master renames its co-threads too.** `ordimor` → `realm` takes `ordimor-farming` to `realm-farming`, for the same reason. The whole set is checked first, so a clash or a running turn anywhere in it refuses the rename before anything moves.
- **The rules, and what they answer.** A missing thread is 404. A bad name, the same name, or a rename that would cross a hosted app's namespace boundary is 400, with the rule spelled out. A taken target or a thread mid-turn is 409. A namespace's base thread cannot be renamed at all: its name is the namespace.
- **Attribution follows.** In a shared memory, entries tagged by the old name are re-tagged by appending a corrected line per entry, never by rewriting the index.
- **Not an immutable id.** Task 185 argued that keying only the tag on an id would be a bridge; the name stays the key for every artefact and the rename moves them all. Old messages keep the names that were current when they were written.

## v1.0.44

Patch release. A model that is not set up now says so, instead of claiming to be Haiku.

- **"Not configured" is now a real state.** The two gateway-owned model settings, the stall detector's and the background workers', read "Haiku (default)" when nothing had been chosen. That Haiku was a bare command-line alias in no catalog, picked by nobody, and unreachable on any install without a signed-in `claude` binary. On such an install every call quietly failed, so stall detection never nudged and Refresh never ran, while the screen said both were on Haiku. The dropdowns now read "Not configured — no stall detection" and "Not configured — Refresh unavailable" until a model is chosen.
- **Nothing runs on a model you did not name.** When a setting is unset the gateway no longer spawns anything, and it warns once per situation rather than on every call. Previously one path, a closed credential gate, would switch which model answered without telling anyone. The "claude" choice now runs the command line with no model flag, and an API key for another provider can no longer capture it.
- **Refresh fails loudly and points at the setting.** With no worker model configured, the `/tasks` drawer's Refresh now refuses with a message naming "Background workers use", instead of failing silently.
- **What this changes for a fresh install.** Stall detection is off until someone picks a model. It was already off on any install without a working `claude` binary; the difference is that the screen now admits it. This supersedes the v1.0.40 line "Both default to Haiku."
- **Still hardcoded, not fixed here.** The `sub_query` tool and the LLM summary step still assume Haiku the same way and are broken by default on a non-Claude install. That is its own task.

## v1.0.43

Patch release. When several threads share one memory, everything a thread retrieves now says whose it was.

- **The main thread now sees who stored what.** Co-threads share their master's memory, and each stored item has always recorded which thread put it there. But that tag was only shown to co-threads; the master thread opened the very same memory with attribution switched off, so a co-thread's finding reached it with no sign it came from elsewhere. That is how a thread could retrieve a sibling's idea and recall it as its own. The master now sees the tag too.
- **The tag says "not yours".** Retrieved items are now marked `[from ordimor-farming]` naming only the _other_ threads that stored them, `[from ordimor-farming, and you]` when you were one of them, and nothing at all when the work was your own. Search results say the same in a `storedByYou` field. Previously the tag read "via" and named the reader's own thread as readily as a sibling's.
- **The model is told what the tag means.** Threads whose memory is shared get one line in their instructions: content marked from another thread is that thread's work, not something you did or discussed, and to say so if you rely on it. Threads with a private memory are untouched.
- **A thread's own notes are now a kind of their own.** Findings, decisions and ideas a thread writes down with `store_content` were filed under the same heading as captured command output. They are now stored as `agent_note`, so a sibling's opinion and a sibling's file read no longer look like the same thing. Existing entries are left as they are.
- **No immutable thread id, for now.** Threads are identified by name everywhere, and renaming is not yet a feature. The reasoning and the list of everything a rename will have to move are recorded in the task document, so that work starts from a plan rather than a rediscovery.

## v1.0.42

Patch release. Your task list no longer has to be configured — it is simply the project's `Tasks.md`.

- **A new thread's tasks work on the first open.** Creating a thread wrote a `Tasks.md` into its project, but the only thing saved to the thread's settings was the project folder, so the file was never on the always-include list that the prompt, the `/tasks` drawer and Refresh all searched. The drawer then said the thread had no tasks file, and it was right — nothing was looking. There is now one rule everywhere: the tasks file is `Tasks.md` at the root of the project folder. No setting, nothing to add.
- **It is sent on every turn automatically.** A migrated file is still projected down to the task in hand. If you also listed it under always-include, it is sent once, not twice, whatever spelling you used. The only way to keep it out of a thread is the existing per-thread exclude.
- **Refresh creates the file when there is nothing to convert.** A project with no `Tasks.md`, or one holding nothing but a title, gets the empty schema written directly: no model is asked, and no git repository is required, because nothing can be lost. Converting a real free-form file still needs git, since that rewrite's undo point is the snapshot commit.
- **The drawer's empty state says what the button will do** instead of telling you to edit your always-include list.

## v1.0.41

Patch release. Refresh no longer rewrites the finished half of your task file.

- **`## Done` is now withheld from the worker entirely.** The first live press of Refresh made twelve edits and every one of them appended "the reload has happened" to a note on a _finished_ task — because the one fact Refresh is handed that it cannot read from the file, the version the gateway is running, is only ever mentioned in notes that live in Done. That section is the record; the format's own rule is that it is never sent to a model. It is now split off before the prompt is built and re-attached unchanged afterwards, so the model cannot edit what it never sees. The prompt is smaller for it.
- **An empty queue is refused before anything happens.** No snapshot commit, no model call — just "nothing to refresh". Given a file with nothing left to do, a model will find something to do anyway. The exception is a file that does not parse: a queue that reads as empty because the file is broken is exactly the case Refresh exists to fix, so that still runs.
- **A task the worker judges finished stays in the queue with a ✓.** Moving a task out of the queue is Close and Archive in the drawer, which is your decision, not the worker's.

## v1.0.40

Patch release. A **Refresh** button in the `/tasks` drawer brings an old task file up to date and re-checks what every task's state actually is — run by a background worker you never see.

- **Refresh converts a task file that was never converted.** Press it on a project whose `Tasks.md` is still free-form prose and it comes back in the current format, every task kept, with its own copy of the manual. That is one press per project instead of one migration per project, and the drawer's empty state now says so rather than only telling you what is missing.
- **It also re-reads the states.** Rows still claiming a change is "published, needs reload" are checked against the version the gateway is actually running, and any status the worker changes has to say why in a note. A status it cannot ground in evidence is left exactly as it was.
- **Your file is committed to git before anything is written.** That is the whole basis on which Refresh writes straight away instead of asking first — so if the file is not in a git repository at all, Refresh refuses to run and tells you why, rather than rewriting something you cannot undo.
- **An answer that loses a task is thrown away whole.** A model handed an entire file and asked for an entire file back will eventually drop a row. Every answer is parsed and checked against the ids that went in before a byte is written; if any id is missing, if it does not parse, if it is empty or if it is not a task file, nothing is written and the drawer says which tasks went missing. The git snapshot is the second line of defence, not the first.
- **The work happens in a background worker with no thread, no history and no sidebar row.** Pressing Refresh does not start a turn, does not make the thread look busy, and cannot suppress anything that watches for a thread that has stalled. Only one refresh runs per thread at a time — a second press while one is in flight is refused rather than queued, because two of them rewriting one file is the thing worth preventing.
- **Background workers get their own model setting**, under "Background workers use" in Settings. Rewriting a whole task file and judging every task's state is a harder job than the yes/no check the stall detector makes, so it is a separate dial rather than a shared one. Both default to Haiku.
- The toolbar at the top of the drawer now shows Refresh whenever nothing is ticked, and the selection actions when something is.

## v1.0.39

Patch release. Tasks can be closed or archived from the `/tasks` drawer, and commenting on them happens in the drawer instead of a panel you could not see.

- **The Comment button now shows you something.** It was opening the same floating editor the chat uses for a highlighted quote, anchored just under the button — and the button sat at the bottom of the drawer, so the editor was drawn below the panel's own floor, where the panel clips it. It was created every time, on top of everything, and entirely invisible. The drawer no longer uses that editor at all.
- **The selection bar moved to the top of the drawer, and only appears when rows are ticked.** It carries Comment, Closed ✓, Archive and a × to clear the selection. A permanent "0 selected" strip would cost a row of the list on every open to answer a question nobody asked.
- **Comments are written inside the drawer, directly below what they are about** — under the row you clicked, or under the selection bar for several rows at once. Clicking a task's title comments on that one task, whatever else is ticked. The chip that lands in your composer is identical to one made by highlighting text in the chat, because both paths end in the same place.
- **Rows can leave the queue.** **Closed ✓** marks a task that worked and **Archive** marks one dropped, superseded or gone stale — a new `[⊘]` status, distinct from `[❌]`, which is a verdict on work that actually happened. Both move the row to the top of `## Done` with today's date, which is exactly what the file's own manual already told an agent to do by hand, and neither is ever sent to a model again.
- **Finishing a blocker unblocks what it blocked.** Anything indented under a closed or archived row moves one level left, so it becomes blocked by whatever blocked its blocker — or by nothing. Nothing is dropped, and the queue's "a blocker is always above what it blocks" rule still holds.

## v1.0.38

Patch release. A document that merely _mentions_ the tasks-file marker is no longer read as a task list.

- **An agent's rules file was being projected as an empty task list.** A `Tasks.md` is recognised by a marker comment inside it, and the check was a plain substring test — so any document that quotes the marker matched. Writing that marker into a sentence of @ordimor's `RULES.md` (as part of the instruction telling it how to convert its own task file) made the rules file itself match: **13,402 characters projected down to 2,010**, with Rules 0–10, the standing constraints and the session checklist silently dropped from every prompt on that thread. The marker must now stand alone on its own line within the first ten lines of a file, which is where both converted files carry it.
- **The manual now documents the marker, inside a code span.** It never mentioned it before, which is precisely why the placement had to be spelled out in a rules file in the first place. Writing it in a code span means a document quoting the manual still does not match.
- **A file the parser cannot fully read is passed through whole instead of projected.** Everything under a heading the parser does not recognise was being skipped, so a broken file lost content from the prompt and reported only a count of bad lines. It now degrades to exactly the old behaviour — the file costs its full size, which is visible, rather than losing content, which is not.

## v1.0.37

Feature release. `Tasks.md` becomes a machine-managed format, with a `/tasks` drawer to reorder it — and stops costing a fortune on every turn.

- **Finishing a task used to make every future turn more expensive.** A project's `Tasks.md` is sent whole with every message, and 64% of this repo's was write-ups of work already completed. It grew by 1,822 tokens in a single evening from three tasks that were _done_. The file now has a strict structure, and only the part that matters is sent: the task in hand in full, the rest of the queue as title lines, finished work not at all. Measured on the real file: **8,976 → 734 tokens per turn**.
- **The format is written inside the file itself.** A `## Format` section states the grammar; the parser skips it (its examples deliberately look like task rows) and it is always included in the prompt, because a model that edits the file needs the rules it is held to. There is exactly one copy of that text — new task files are seeded from it, and a test locks it against this repo's own file so the instructions cannot drift from what the parser enforces.
- **Dependencies are indentation.** A row indented under another is blocked by it, so a blocker always sits above what it blocks and the top of the queue can never be blocked — "what's next" is literally the first line.
- **A new `/tasks` drawer** slides in from the right: drag to set the build order, tick one or more rows to comment on them through the same popover the chat uses for highlighted text, and accept or reject the dependencies proposed during migration.
- **A drag can never separate a task from its blocker.** Siblings reorder freely and a whole dependency cluster moves as one unit; anything that would pull a task away from what blocks it is refused with a reason, and nothing is written.
- **Nothing is written to a file the parser did not fully understand.** Unreadable lines are shown in the drawer rather than skipped, and every write is refused while any remain — serializing a partial parse is how a drag silently deletes a line.
- **Existing task files are untouched.** The new handling is switched on by a marker line inside the file, so every other project's `Tasks.md` is read exactly as before until it is migrated.

## v1.0.36

Patch release. A thread that is waiting on a background job is no longer told it has stalled.

- **40% of stall nudges were false alarms, and the gateway already knew.** The stall detector decides whether a thread trailed off mid-task by reading its last few sentences. But the most common way a thread correctly ends a turn is by starting a background job and stopping — which is exactly what it is instructed to do ("end your turn, do not poll, you are woken automatically"). A thread that obeys writes "ending the turn here; the job wakes me when it exits", which reads to the detector as trailing off. Measured on disk: **47 of 118 nudges fired while a job was literally running on that thread** — a floor, since older job records are pruned and their nudges can no longer be attributed.
- **The fix consults facts already on hand rather than guessing harder.** Before nudging, the detector now asks the job registry whether a job is running, and the thread's config whether a one-off scheduled trigger is armed for the future. Either means a wake is already guaranteed, so the nudge is redundant. This also skips the classifier model call entirely on those checks.
- Recurring (cron) schedules deliberately do **not** count — a daily 09:00 report is not a wake for the work in hand, and treating it as one would silently disable the detector on every thread that owns a recurring schedule.
- A thread stopped by the repeated-tool-call guard is still nudged even with a job running: that is an observed failure to make progress, not an inference from prose.
- Every suppressed check names its reason in the gateway log.

## v1.0.35

Patch release. The gateway's "you paused before finishing" nudges are no longer drawn in the chat.

- **Stall-mitigation nudges are hidden from the conversation.** These are messages the gateway sends to a thread it believes stopped early. They were rendered as though the user had typed them — 107 of them on disk, 105 on one thread over four days. They are still stored, and still visible in the message-selection overlay, so they can be reviewed or deleted; they are simply no longer drawn in the chat.
- **A nudge aimed at a thread that has already resumed is now dropped, not delivered late.** Deciding whether a thread stalled takes a few seconds; if the thread started working during that window, the nudge used to be queued and delivered afterwards — telling a busy thread it had paused, quoting a sentence from two turns earlier.

## v1.0.34

- Background jobs and scheduled tasks now show clearly as automated senders in chat

## v1.0.33

Patch release. A third of the past-conversation context in every prompt was the same messages printed twice. Fixing it cuts retrieved-context size by a quarter on a real thread with no loss of recall.

- **32.6% of all rendered history lines were duplicates.** Measured over 3,084 prompt captures on the gateway (1,078 with a `[Relevant Past Messages]` block): 8,327 of 25,554 lines were re-renderings of a message index already present in that same prompt — exact identity, not a similarity heuristic, since every line carries its own `#index`. That cost 6.51M of 18.51M history tokens (35.2%), affected 59% of captures, and ran as high as 42.8% on individual threads. Multiplicity was always `2x` and never `3x` anywhere on disk — the bounded distribution that pins a single emit-once-more code path.
- **Cause: `expandWithSegments` emitted each hit twice.** Expanding a conversation segment emits every scored result inside it; the outer loop then reached those same results and emitted each again. The guard that looked like it prevented this tested a _message_ index against `processedSegments`, which holds _segment start_ indices — two number spaces in one Set, so it only fired when a hit happened to sit exactly on a processed boundary. `searchHistory` is clean (Set-merged); `formatRetrievedContext` does not dedupe, so every duplicate was billed twice against the RAG budget and displaced a different message that would otherwise have fit.
- **Fix: one `emit()` gate owns uniqueness** (Rule #8 — no second, defensive dedupe downstream, which would be a bridge between two notions of "unique"). The surviving copy keeps the real search score, never the segment decay.
- On the `fable-config-amnesia` bench case (a real 3,809-message thread): history messages 81 → **49**, retrieved context 8,011 → **5,991** tokens (−25%), full prompt −15.7%, **identical recall** (3/3 gold facts), stored-content items unchanged. Where the budget is not binding the prompt simply gets smaller; on the budget-capped turns — whose history blocks were 41.6% duplicates — the freed space is instead spent on additional distinct messages.
- Recorded but deliberately **not** changed: a purely-expanded neighbour scores `segmentBest × 0.6` while task 089's relevance floor drops anything under `max(0.15, topScore × 0.7)`, so since 089 segment context has been unreachable and the duplicate was the whole of segment expansion's observable output. Reviving it (raise the decay) or removing it (Rule #8) is a benchable dilution decision, not a bug fix.
- RLM-touching: bench pass — 2/2 real cases, 12/12 ladder, static prompt 1791/1800 untouched, speed line held against a same-session control run on unmodified HEAD. 1657/1658 tests green (+4; the one failure is a pre-existing `migrate.test.ts` network flake that fails identically on unmodified HEAD under full-suite load), tsc clean, build clean, lint delta 0, negative control proven.

## v1.0.32

Patch release. The adaptive context budget's feedback loop can run in both directions for the first time — its "how full was this prompt?" sensor was pointed at the model's response instead of the prompt. Also picks up `@luckydraw/blex@0.1.19`, so mermaid diagrams render without text spilling outside their boxes.

- **The adaptive budget has never once grown, on any thread.** Measured across all 91 `*.adaptive.json` sidecars on the gateway: 0 GROW, 107 SHRINK, 813 FLAT — 49 threads sitting on the 100,000 floor, including every high-traffic one. `recordTurn`'s `tokensUsed` is documented as "tokens actually packed into the prompt", but the gateway fed it `estimateTokens(fullResponse) + userQueryTokens` — the assistant's **output** plus one prompt component, excluding the system prompt, the recent-conversation window and **all retrieved context**, the largest part and the one the budget exists to size. Growth needs `tokensUsed ≥ 55,250` at the floor; the largest value that shape ever produced anywhere is 7,615. Shrink needs TTFT alone, so it worked — hence a one-way ratchet.
- **One helper, every caller.** New exported `packedPromptTokens()` sums the five real components (instructions, always-include, recent window, RAG, user message). The gateway feeds it from the figures it already computes for `debug.systemPromptBreakdown`; the agentic path overwrites the instructions figure with its own text, since that branch sends a different instruction block. The TUI, which fed a _third_ definition (the RAG slice alone), now uses the same helper — a source-level test fails if either call site diverges again (Rule #8).
- **Capacity is measured against the budget the retriever actually gets.** `atCapacity` compared against a private `effectiveBudget()` (`0.75×CL − 10,000`) while packing used `getTotalContextBudget()` (`CL × 2/3`) — two notions of "the budget" in one class, ~55k apart at a 775k pin. The private one is deleted, not kept alongside.
- Policy constants are untouched, but this is **not** a no-op: the grow branch is now reachable, so a thread that packs ≥85% of its budget and answers in under 4s can grow by 1.2×. Whether larger budgets help is a separate, benchable question (task 088 P3 measured dilution as the real failure class) — this restores a mechanism that only ever ran one way, and makes the sidecars record real packed totals.
- RLM-touching: bench pass — 2/2 real cases, 12/12 ladder, static prompt 1791/1800, speed line held against a same-session control run on unmodified HEAD (the differ's flag against the older `after-162.json` baseline was machine drift, not the change). 1654/1654 tests green (+9), tsc clean, build clean, lint delta 0, three negative controls proven.

## v1.0.31

Patch release. The stall classifier now gets an output budget a reasoning model can actually finish in — closing the last case where it failed closed and never nudged.

- **The classifier's 16-token output budget was doing the killing that task 166's `reasoningEffort: 'low'` didn't.** On both HTTP providers `maxTokens` maps to the endpoint's total-output cap (`max_tokens` / `max_output_tokens`), which bounds reasoning **and** content together. A low-effort reasoning model still spent more than 16 tokens thinking and ran out mid-reasoning, never reaching the content phase — so the verdict was empty, the classifier failed closed, and a stalled thread sat parked with no nudge. The budget is now 512 (`CLASSIFIER_MAX_TOKENS`): room to finish thinking and still emit the one-word verdict. The Claude CLI branch is unchanged.
- Not RLM-touching (no prompt/budget change to a thread's own turns). 1645/1645 tests green (+2), tsc clean, build clean, lint delta 0, negative control proven (reverting the constant to 16 turns exactly the new tests red).

## v1.0.30

Patch release. The stall detector now works when the classifier is a reasoning model, and a thread that livelocks gets nudged to continue instead of parking itself.

- **The stall classifier (task 164/165) was structurally dead for reasoning models.** When the configured `stallClassifierModel` was a reasoning model (e.g. Qwen3.8-27B), it spent its entire small output budget thinking and emitted no content, so the classifier read an empty answer, failed closed, and never nudged — the fallback signal silently did nothing. It now sends `reasoningEffort: 'low'` (a trivial binary read of a few sentences must not deliberate) so the model finishes thinking inside its budget and actually talks.
- **The verdict is now parsed from the streamed `text` events, not `response.content`.** Both HTTP providers emit `text` events for content only, but `response.content` folds the model's private reasoning in as a last-resort text block — so reading the blocks could hand the parser scratch paper that happens to contain "YES". Reading the events is the channel guaranteed to be the actual one-word answer; if no event ever arrives it still fails closed, exactly as before. The Claude CLI branch is unchanged.
- **A livelock exit is now a definite nudge.** When the task-160 livelock guard stops a turn (identical tool call repeated 6×), the loop flags the result, the gateway stamps `livelocked: true` onto the persisted message, and the stall check short-circuits — nudging immediately with a livelock-specific message ("take a different approach — do NOT re-issue the same call — or summarize what you have so far") instead of consulting the classifier. Without this, the classifier read the "I stopped because I was looping…" line as a question back to the user and correctly declined, leaving a thread told to work continuously parked and waiting for a human. The existing per-message backoff still bounds repeat nudges.
- Not RLM-touching (no prompt/budget change to a thread's own turns). 1643/1643 tests green (+7), tsc clean, build clean, lint delta 0, three negative controls proven.

## v1.0.29

Patch release. The stall-detection classifier can now run on any model, not just Anthropic.

- **The stall classifier (task 164) was hardwired to the Claude CLI** — `claude --print --model <id>` — and its settings dropdown only listed Anthropic sub-models. A HuggingFace / OpenAI / custom-provider model couldn't run through the Claude CLI at all, so the operator could never actually pick one.
- **The classifier now routes through the same provider resolution every other model uses** (`resolveModelProvider`, task 118/147) and branches: a Claude CLI sub-model runs as a one-off `claude --print --model <id>` call; any other catalog model runs as a one-shot HTTP `chat()` through the provider the resolver picks. The prompt and the fail-closed contract are identical on both branches.
- **Claude sub-models are detected by `claudeModels[]` membership before touching the resolver**, so a Claude sub-model is never misrouted over HTTP when a HuggingFace key is present (a regression test locks this). A closed gate — no credential for the resolved provider — degrades to "haiku" rather than spawning a doomed HTTP loop.
- **The settings-UI dropdown now lists the full catalog grouped by provider** (same shape as the voice-model selector), with "Haiku (default)" as the clear option. `resolveStallClassifierModel` accepts an id from either catalog; unset/blank/stale still falls back to "haiku".
- Not RLM-touching (no prompt/budget change to a thread's own turns). 1636/1636 tests green, tsc clean, build clean, lint delta 0.

## v1.0.28

Patch release. Threads that stop mid-task get nudged to continue.

- **A thread that ends a turn on a sentence that promises the next action** ("Let me look at how `inspect.ts` renders claim rows.") **and then stops, with no `<todo>` block and no `schedule_trigger` armed, used to just sit there** — not caught by empty-turn recovery (the turn isn't empty) or the turn cap (nowhere near it).
- **Now, when a thread goes idle, a single quiet-window timer (~75s) is armed for that thread; any new streaming before it fires cancels it outright**, so there's never more than one pending check and never wasted work on a thread that's correctly done. On fire, the check runs once against the freshest possible state: an unchecked `<todo>` item in the last assistant message nudges directly (free signal); otherwise a cheap-model classifier over the last 5–6 sentences nudges only when it's _obvious_ the thread meant to continue — default no, since prodding a thread that's correctly idle and waiting on the user is the worse failure.
- **The nudge arrives as an ordinary injected turn** ("You paused before finishing: '…'. Continue.") via the existing agent-inject path, with a recognizable automated sender. Repeat stalls on the _same_ message cap out after 2 nudges; a genuinely new stall on a later message gets a fresh try.
- **The classifier model is a new system setting** (`stallClassifierModel`, default Haiku) with a settings-UI card mirroring the voice-model one — pick any Anthropic sub-model from the dropdown, or "Haiku (default)" to clear it.
- Also fixed a broken `claude --print --input-format stream-json` invocation (missing `--output-format stream-json --verbose`) that made the classifier fail closed on every call — the same breakage exists in `generateLLMSummary` (content-detector.ts), left untouched here (out of scope); it silently falls back to the heuristic summary today.

## v1.0.27

Patch release. Tool-turn narration text streams live again, for every model.

- **Task 121 (Aug 4) buffered all text on a tool-bound turn until the turn ended**, to stop a model's confused planning prose (e.g. "I will guess the tool name") from streaming as if it were an answer — a real incident on Kimi K2.7. The side effect: legitimate narration ("let me check the code", "trying this other approach") was silently dropped for every non-Claude model, on every turn, whenever it landed alongside a tool call.
- **Reverted.** Text now streams the instant it arrives, tool-bound or not — matching the pre-121 behavior and Claude CLI threads, which were never affected. Karl chose this over hiding-with-a-visual-cue or leaving it as-is: narration back for everyone, accepting the Kimi-style risk returns with it.

## v1.0.26

Patch release. `/include` now lets you remove a global always-include file from a single thread.

- **Clicking remove on a global-scoped include row used to do nothing visible** — and on any thrown error, would have silently deleted the file from the _global_ config, affecting every thread. Neither behavior was intended.
- **One remove button now works for every row, regardless of scope.** A global file is added by default and removable just like a thread-owned one: removing a global entry excludes it for this thread only (every other thread is untouched); removing a thread-owned entry deletes it for real. Click again to re-include.
- No new UI affordance and no separate "delete globally" action — same button, same click, scope-aware underneath.

## v1.0.25

Patch release. The `/select` overlay now lists the most recent messages first.

- **The list was in chronological order**, so the messages you're most likely to want to delete (recent ones) were at the bottom of a long scroll. It's reversed now — newest at the top, matching how the conversation itself reads. One-line change in the shared row renderer, so both the standalone `/chat` panel and the embedded widget are covered.

## v1.0.24

Patch release. The `/select` panel is now a compact overlay, matching `/revert`.

- **`/select` used to bolt checkboxes onto the full live message bubbles** in the actual conversation view — scrolling through complete rendered content (including blex blocks and code) just to pick messages to delete.
- **It now opens a dedicated overlay**, styled like the existing `/revert` list: one compact row per message (role badge, one-line preview, timestamp, checkbox), with the same header/close chrome and a footer bar for selection count / Select All / Delete. The live thread underneath stays undisturbed while selecting.
- Implemented once and shared between the standalone `/chat` panel and the embedded widget, since both already shared the row-rendering and delete-confirm code.

## v1.0.23

Patch release. Search result snippets now show why a match was found, and the model is told to search before claiming something isn't on record.

- **`search_history` snippets always showed the first 200 characters**, regardless of where the query actually matched. A relevant hit whose matching text sat past character 200 produced a snippet that looked entirely unrelated to the query — a real retrieval investigation on the pursuit thread traced a "nothing on record" denial back to exactly this: the right message was returned, but its snippet gave no visual signal that it was a match.
- **Snippets now center on the earliest matching query term** and **bold** it, so a skimming reader (model or human) can see at a glance why the result came back. `search_content` already had this from task 082 — this closes the gap in the sibling tool.
- **One line added to the system prompt:** before telling the user something is not on record, not mentioned, or doesn't exist, the model is now told to call `search_history` or `search_content` with the specific terms first. RETRIEVED CONTEXT is a best-effort automatic pass keyed on the current message — its being empty is a reason to search, not evidence of absence. Same caveat as prior prompt nudges (tasks 124, 156): this changes what the model is told, not what it's forced to do.

## v1.0.22

Patch release. Long agentic turns now re-aim their memory at what the model is doing _now_, and the 30-turn ceiling is gone.

- **The memory was frozen for the whole turn.** The retrieved-context block was assembled once, against your opening message, and then carried unchanged through every round. Measured on a real turn: 68k tokens of round-1 memory riding along while the model had moved on to eight co-threads' worth of file edits — and that fixed ~103k-token base was resent on all 30 rounds, which is 3.1M of the turn's 4.18M input tokens. Cost was dominated by a constant paid thirty times, not by growth.
- **It re-retrieves at every step now.** The loop replaces the retrieval block in place at each step boundary, aimed at what the model just did — your goal as the anchor, plus the recent tool names and their file paths/commands/queries and the tail of the last text. Round 20 gets memory about round 20's work instead of round 1's.
- **Refreshing is free on the cache.** The cached prefix (instructions + always-included files) sits _before_ the retrieval slot, so swapping it leaves the cached prefix byte-identical. There's a guard: if a future reordering ever puts a cached block at or after the slot, it refuses to refresh and degrades to today's frozen behaviour rather than silently re-paying for the whole prompt every round.
- **The 30-turn cap is removed.** It was the loop's only bound on ordinary progress (the cumulative-output limit is checked only inside the truncation-retry branches). A model that wants to keep going is no longer forced to stop at 30; `maxTurns` remains an option for callers that want a ceiling.
- **What still ends a turn:** the model emitting a turn that isn't a tool call (the normal exit), a new livelock guard (the same set of tool calls repeated byte-for-byte on five consecutive rounds), task 156's empty-turn counter, your Stop button, and a gateway reload/restart. None of those is a length bound.
- **Stated cost, not hidden:** an unbounded turn on a ~100k-token base is real money — at the measured rate a 100-round turn is ~14M input tokens. The livelock guard stops a stuck model, not an expensive productive one.
- Affects direct-provider threads only (`@ordimor`, `@thundercat`). Claude CLI threads run Anthropic's own loop and are untouched.

## v1.0.21

Patch release. Co-threads you create under a hosted app now show in the sidebar instead of being hidden with the app's visitor threads.

- **A co-thread under a hosted app was invisible.** The sidebar hides the app's ephemeral visitor threads to keep the list clean, and a co-thread's name is shaped exactly like one of those visitor threads, so the same rule that hides the visitors swept the co-thread up with them. The co-thread was created correctly and worked fine — it just never appeared in the list.
- **The fix tells the two apart by registration, not by name.** A co-thread you deliberately create is recorded as such; an app-minted visitor thread is not. The list now shows a namespaced thread only when it is a registered co-thread, so your deliberate co-threads appear (nested under their app) and every visitor thread stays hidden.
- **Nothing else changes.** App keys still see nothing, and apps that are not hosted (no visitor threads) are unaffected — their co-threads already showed.

## v1.0.20

Patch release. Turns started by a timer or by a finished background job now run on the model the thread is configured for.

- **A thread woken by a schedule or a completed job could silently run on Claude.** The gateway hands each turn a bundle of settings that includes which model to use and the credentials to reach it. Turns started from a browser, from the API, or by a message from another thread got the full bundle; turns started by the scheduler or by a finished background job got a stripped-down one built separately, with the model settings missing. With no model named and no key, those turns fell back to Anthropic.
- **It depended on timing, which is why it looked random.** If the thread was already busy, the wake-up was queued — and the queue passes the full settings, so the model was correct. If the thread was idle, it was not. The same event could go either way.
- **Two consequences beyond the wrong model:** those turns billed against the Anthropic subscription rather than the configured provider, and three fixes shipped earlier the same week apply only to the non-Anthropic path, so on these turns none of them were in effect.
- **The duplicate settings bundle is deleted**, not filled in. There is now one, shared by every path the gateway starts a turn from, which also means a change made in Settings reaches scheduled and job-woken turns immediately, with no restart.
- Also fixed: a scheduled turn on a thread belonging to a hosted app now gets that app's own tools, which it had never been given.

## v1.0.19

Patch release. Threads using non-Anthropic models now get the gateway's full toolset instead of a quarter of it.

- **Non-Anthropic threads had 4 of the gateway's 18 tools.** Threads on Claude receive every gateway tool through a standard connection; threads on other models were handed four hand-written copies — see other threads, message a thread, push a notification, upload a file — and nothing else. Missing were self-scheduling, background jobs, co-thread creation and merging, pipeline updates, and email. The instructions those threads read described all of them, so a thread following an instruction shipped the same day found the tool did not exist and had to say so.
- **They now connect to the same tool server the Claude threads use.** The four hand-written copies are deleted rather than extended: two of them had already drifted from the real tools they imitated. A tool added in future reaches both kinds of thread with no second change.
- **What this restores in practice:** a thread can schedule its own wake-up, run long work as a background job that survives the end of its turn, and list and merge its co-threads' work back into the main branch.
- Affects threads on non-Anthropic models only. Threads on Claude are unchanged — they already had all eighteen.

## v1.0.18

Patch release. Two fixes for threads told to work continuously: they no longer get stopped part way, and they can now arrange to pick up where they left off.

- **A thread working through a long task is no longer stopped by the recovery machinery.** When a reply comes back empty, we ask the model to try again — and that request used to say "don't use any tools, just write an answer". Correct when it was answering a question and only needed to say what it already knew; exactly wrong when it was in the middle of doing work, because it told the thread to down tools and write a status report. That is what ended a run of work with a dozen items still to go and most of its allowance unspent. The request now depends on what the thread was doing: mid-work, it is asked to carry on.
- **The retry limit now counts consecutive failures, not lifetime ones.** It was a single allowance for the whole run — right for one question, far too tight for a session of dozens of steps. Any step that actually does something resets it, so a long run is bounded by the ordinary overall limits rather than by one bad moment early on.
- **Threads can now arrange to continue their own work.** The ability to schedule a wake-up already existed and worked; nothing had ever connected it to a thread's own unfinished business. So even a flawless long run simply stopped at the end and waited to be asked for a status. A thread that stops with work outstanding is now told to schedule its own resumption. Threads that cannot schedule never see the instruction.
- Affects threads using non-Anthropic models for the first two; the third applies to any thread that can schedule.

## v1.0.17

Patch release. One fix: threads using non-Anthropic models now get told how hard to think.

- **Your effort setting finally reaches non-Anthropic models.** Every thread has an effort level — low through max — and it was only ever sent to Anthropic models. For a model like Qwen or DeepSeek we sent no instruction at all, so the model decided for itself how long to deliberate. Measured against a real failing prompt, that meant a reply landed only about half the time: the rest of the time it thought until it ran out of room and said nothing. Sending the setting fixed it in every one of sixteen test replies, and cut the wait by roughly 40%. If you set an effort level months ago on one of these threads, it starts working now.
- **The effort dropdown appears on every model, not just Claude.** It was previously hidden unless the thread was on an Anthropic model. Threads that have never had a level chosen get "medium", which tested flawlessly.
- **The levels adapt to what each model understands.** Anthropic models understand five levels; the OpenAI-style endpoints understand three, so the top two fold down to "high" rather than being dropped. The dropdown hides the two levels a given model cannot honour, so it never offers something that will not happen.
- **An endpoint that refuses the setting no longer breaks the reply.** If a provider rejects it, we quietly drop it and retry rather than failing the turn.

## v1.0.16

Patch release. One fix: a hard question could make a thread answer with its own private thinking instead of an answer.

- **A thread no longer runs out of room to answer because it spent it all thinking.** Threads had a limit on how much they could produce in one go. Newer models think privately before they reply, and that thinking was being charged against the same limit — so on a question that needed real deliberation, the entire allowance went on thinking and there was nothing left to say it with. The harder the question, the more certain the silence. The limit is gone; other safeguards already covered what it was added for.
- **A cut-off thought is no longer mistaken for a cut-off answer.** When a reply was interrupted partway, the recovery machinery assumed it was interrupted _writing_ and asked the model to carry on from where it left off — quoting an empty space, because nothing had actually been written yet, and handing back the model's private notes as though they were its published answer. Being told to continue from nothing tends to make anyone start over, which is why this showed up as a thread repeatedly re-thinking out loud. An interrupted thought is now simply discarded and the attempt retried with more room.
- Affects threads using non-Anthropic models.

## v1.0.15

Patch release. Voice mode gets its memory back and its own model; threads can see what their co-threads are doing.

- **Voice mode was running with no memory at all.** The voice instructions were being used to _replace_ the thread's normal briefing rather than be added to it, so a voice turn arrived with no recent conversation, no retrieved memory, no always-included files and no tools — while the work of searching memory was still being done and then thrown away. This is why voice mode felt vague and forgetful compared to typing. Voice turns now get everything a typed turn gets, plus the voice rules on top.
- **Voice mode can use its own model.** A new "Voice mode uses" setting sits beside the default model in Settings, so hands-free conversation can run on a faster or cheaper model than the thread normally uses, without changing the thread. Leave it on "Same as thread" for no change.
- **Voice mode hands heavy work to a helper thread.** Instead of a vague instruction to delegate, a voice thread now creates a single long-lived worker that shares its memory and its project, sends it the job, and gets on with the conversation. The worker reports back when it is done.
- **Speaking while a report comes in no longer starts two conversations at once.** Previously a message sent while the thread was busy with an incoming report started a second turn on top of the first. It now waits its turn and is delivered when the thread is free — and if it was spoken, the reply comes back spoken too. "Send Now" still forces it through immediately.
- **A thread can now see what its co-threads are working on.** Previously it could only find that out if you happened to ask a question that matched their work. Every turn now includes a short live summary of what each co-thread has open and unfinished, including a warning when two of them are heading for the same file — while there is still time to redirect one, rather than discovering it at merge time. Threads with no co-threads are unaffected.
- **Co-threads now write down what they conclude, not just what they touch.** Files a co-thread reads and commands it runs were already shared; its actual findings were not, because those lived only in its own conversation. It is now told to record them as it goes, so the main thread can answer what a co-thread _worked out_, not just what it _looked at_.

## v1.0.14

Patch release. One fix: a shell command could hang a thread indefinitely.

- **A thread can no longer get stuck forever on a shell command.** When a command started something that kept running on its own — a test run, a build, a server — and then hit the two-minute limit, the limit killed the command but not what it had started. The thread was then left waiting on output that could never arrive: no error, no timeout, no log line, just an apparently stalled thread. It stayed that way until the leftover process happened to finish, which could be hours. Commands now finish as soon as the command itself exits, whatever it left behind, and anything a timed-out command started is stopped along with it. If a command exits cleanly but deliberately leaves something running, the result now says so and points to the proper way to run long work in the background. This affected threads using non-Anthropic models.

## v1.0.13

Patch release. Three fixes to things that failed silently.

- **A thread's stored memory can no longer be damaged by two parts of the system writing at once.** The index that lists everything a thread has stored was rewritten in full during cleanup passes, with no lock and from a snapshot that could already be out of date — so a save landing at the wrong moment could tear a record in half, or be erased outright. One damaged record made the whole index unreadable, which silently blinded a thread to everything it had ever stored. Cleanup now takes a lock and re-reads from disk before rewriting, saves take the same lock, and a damaged record is skipped and logged rather than taking the rest of the file down with it. One affected store on this machine was repaired; all others were checked and are intact.
- **A reply is no longer cut off part-way through.** Some markers are stripped out of a response as it streams to you. A stray or quoted marker opened a block that never closed, and everything after it was dropped — with no error and nothing to indicate it had happened. The stored copy was always complete, so this was only ever a display problem, but it meant a reply could quote its own formatting and destroy itself. Markers now only take effect at the start of a line, and anything still held back when a response ends is shown rather than discarded.
- **A turn that produces only reasoning no longer ends in silence.** With some models, a turn could finish having thought about the question but produced no answer, and nothing at all was shown or saved — no message, no error. The model's own reasoning is now handed back to it with a request for the plain answer, once; if that also comes back empty, a short message is shown explaining what happened, with the reasoning included, so no turn can end with nothing.

## v1.0.12

Patch release. Custom OpenAI-compatible providers, and visible reasoning on more endpoints.

- **Add your own model provider from Settings.** Under "Add a custom provider", enter a name (for example Vultr), the base URL from the provider's documentation, and its API key; `/chat/completions` is appended automatically if you paste only the base. Models added under that provider appear in the thread model picker labelled with the provider's name. Each custom provider keeps its own key, separate from the OpenAI and HuggingFace keys, and changes apply immediately with no restart.
- A model that points at an unknown or key-less custom provider falls back to the Claude path rather than quietly going to the HuggingFace router with the wrong key. The public model list shows a provider's name only — never its URL or key.
- Error messages and logs now name the actual endpoint that failed instead of labelling every non-Claude failure as HuggingFace.
- **Thinking is no longer dropped on vLLM-style endpoints.** Some providers send a model's reasoning under the field name `reasoning` rather than `reasoning_content`; that channel was silently discarded, so a turn could look stalled and a reasoning-only answer came back empty. Both names are now read.

## v1.0.11

Patch release. The sidebar now shows which threads are working.

- **Animated dots on a thread that is running a turn.** A thread's name in the sidebar shows three pulsing dots while it is working, and they clear when it finishes. This works for threads you are not watching — one woken by a schedule, by a message from another thread, by a finished background job, or driven from another browser tab — which was the whole point: those threads previously gave the sidebar no signal at all, so a thread could be working for minutes while looking idle. A tab opened part-way through a turn shows the dots straight away rather than waiting for the next change. The animation is suppressed for anyone who has asked their system to reduce motion.
- The per-thread indicator is only sent to the admin view, which is the only place with a sidebar. App visitors are not told which threads exist or when they are busy.

## v1.0.10

Patch release. Fixes charts flickering endlessly in the chat panel.

- **Charts no longer flicker.** On displays set to 150% scaling, a chart in the chat would resize by a single pixel over and over without ever settling, pushing the whole conversation up and down continuously. The cause was in the chart library: a styling rule forced the chart's size while the charting engine was simultaneously setting its own, and the two overwrote each other in a loop. The rule has been removed upstream and this release picks up the fix. Verified on real charts across a range of window sizes and display scalings; other scaling settings were never affected, which is why the problem appeared for some people and not others.

## v1.0.9

Patch release. Co-threads can now genuinely work the same project at the same time: each gets its own private copy of the repository, the master folds the work back in, and the set can see what each other is doing.

- **Every co-thread gets its own private copy of the project.** In v1.0.8 co-threads shared one folder, so two of them editing the same file would silently overwrite each other. Each co-thread now works in its own checkout on its own branch, created automatically and needing no setup. Siblings cannot see each other's edits and cannot clobber them. A project that is not under version control keeps the previous shared-folder behaviour and says so rather than refusing to run.
- **The master folds the work back in.** A single merge action brings a co-thread's work into the master's copy, committing anything the co-thread left unsaved first. If the master's own copy has uncommitted changes the merge refuses rather than risking them, and a genuine conflict is undone cleanly and reported to the master thread with the file names — not left half-merged for you to untangle.
- **A co-thread now knows what it is.** Previously the only co-thread guidance in a thread's instructions was written for the master, describing three actions a co-thread is not allowed to take — while saying nothing about its own situation. A co-thread is now told that it has a master, that it is working in its own private copy of the project, that nothing it does reaches the master until the master merges it, and that it should report back when its piece is done. Without that last part, finished work sat on a branch nobody knew to merge.
- **Co-threads can see each other.** Asking for the co-thread list from a co-thread used to return nothing, because the list belongs to the master. It now returns the whole family from either side — the master, every sibling, and what each of them has in progress. Creating and merging co-threads is still master-only.
- **Collision warnings before the merge, not during it.** The co-thread list now reports any file that two or more co-threads are both changing, so an impending conflict is visible while redirecting one of them is still cheap. This counts work a co-thread has already committed, not just its unsaved edits — a co-thread that has committed its change is exactly the one you would otherwise miss.

## v1.0.8

Patch release. Several threads can now work on one project sharing a single memory, and a message typed while a response is streaming can wait its turn instead of interrupting.

- **Co-threads.** A thread can create co-threads under its own name — `cumulus` creates `cumulus-frontend` — each with its own conversation and history, all reading and writing one shared pool of stored content. Anything one of them reads or finds, the others can search, and results say which thread found them. Co-threads inherit the master's model, effort, context limit and project directory, so there is nothing to configure. Only the master can create them, so the family stays one level deep. Sharing is opt-in per master and is never inferred from a thread's name, so an app's visitor threads are unaffected.
- **Co-threads in the sidebar.** Right-clicking a thread now offers "New co-thread…" alongside Delete, and a master's co-threads group under it behind a disclosure triangle that remembers whether you left it open. The per-thread message count is removed to make room. A thread is only shown as a co-thread when it really shares memory with its master — the sidebar cannot draw a relationship that does not exist.
- **Shared memory is safe under concurrent writers.** Stored-content embeddings are now appended rather than rewritten whole, so two threads saving at the same moment can no longer discard each other's work. Previously this failed silently: the content stayed keyword-searchable but became permanently invisible to meaning-based search. Existing memory files are read identically and need no migration.
- **Queue instead of interrupt.** Typing while a response is streaming now turns the Send button into **Queue**. Pressing it defers the message until the response finishes; pressing Return still interrupts as before. A queued message appears immediately marked QUEUED with Send Now, Edit and remove controls, and several queued messages arrive together as one batch.
- Messages that arrive while a thread is busy now render as messages from the sender rather than as your own, which they never were.

## v1.0.7

Patch release. Closes a blind spot in how an allow-list is turned into a restriction.

- A thread's allow-list is enforced by denying every tool that is not on it, which requires knowing the complete set of tool names. That set was read from what the Claude CLI advertises at startup, and the CLI recognises nine permission names it does not advertise — among them `Glob` and `Grep`, both of which can read files. A name the gateway never knew about was never denied, however the thread was configured. Those names are now included, so an allow-listed thread is restricted to what it lists.
- No change for a thread without an allow-list: the tool inventory is only consulted when compiling one.

## v1.0.6

Patch release. Closes a gap in the file-reading restriction for threads that use an allow-list.

- A thread that lists the tools it is allowed to use, without including the built-in file-reading tool, now has the gateway's own `read_file` tool confined to its own project directory. Previously this confinement only applied to threads written the other way round — as a list of tools to withhold — so the protection did not reach the app visitor threads it was written for, and such a thread could read any file the gateway can read, including the gateway's own configuration and the credentials in it.
- The web-app agent kit's example visitor config no longer allows the built-in file-reading tool. That tool belongs to the Claude CLI and the gateway cannot confine it, so allowing it leaves the filesystem unbounded no matter what else is set. The kit and the guide now explain the distinction and recommend widening the project directory instead.
- Messages sent by the gateway itself — a finished background job, or a scheduled trigger — no longer invite the recipient to reply to them. They are not agents, and replying created an empty conversation named after the job.

## v1.0.5

Patch release. Long-running background work is now owned by the gateway instead of being explained to the model.

- New `run_job` tool, with `list_jobs`, `job_log` and `cancel_job`. A thread starts a long command and ends its turn; when the command finishes, the thread is woken with the exit code and the tail of the output. Jobs are started by the daemon itself, outside the turn's process tree, so a job no longer has to survive the turn that started it — the two failure modes that killed background work (the turn's output pipes closing, and the job dying with the turn) are now unreachable rather than something the model has to work around.
- Jobs survive a gateway restart, and are reported honestly when they do not. A job killed while the gateway was down is reported as interrupted rather than as a failure or a success. Full restart survival also needs `KillMode=process` in the service unit; the generated unit template now sets it, and an existing install can add it by hand.
- Completion delivery waits for a busy thread instead of interrupting it, using the same queue as scheduled triggers and agent messages.
- The background-work instructions every thread received each turn are replaced by a short pointer to `run_job`, and the shell recipe that was seeded into every thread's stored content is removed. This makes each turn's fixed instructions smaller.
- `run_job` is unavailable to threads that are not allowed to run shell commands, so it cannot be used to work around a thread's tool restrictions.

## v1.0.4

Patch release. Threads can now be given a list of the tools they are allowed to use, instead of a list of everything they are not.

- New optional `allowedTools` setting on a thread's config. Naming the handful of tools a thread should have is now enough — the gateway works out the rest and denies them. Previously an app author had to enumerate every tool to withhold, which meant each tool a future release adds was silently granted to every visitor until someone noticed and updated the list.
- The tool list is discovered from the Claude CLI itself at startup, so it stays accurate as the CLI changes, and is combined with the gateway's own tools. A thread with no `allowedTools` behaves exactly as before.
- Instructions a thread cannot act on are no longer sent to it. A thread with no shell access stops receiving the background-work and scheduling sections of the prompt, which cost tokens every turn and invited the model to improvise a substitute.
- The web-app agent kit's example visitor config now uses an allow-list, and the guide explains why an app surface should deny by default.

## v1.0.3

Patch release. Two corrections to what the gateway hands every thread.

- Agents are no longer told they have a `broadcast` tool. The tool was removed on purpose — agents address specific threads — but the instructions still advertised it, so an agent that tried to use it got an error. Messages sent to all threads still arrive; only the misleading instruction is gone.
- `read_file` is now confined to a thread's own project directory when that thread has the built-in file-reading tool disabled. Previously the two were independent, so a thread configured for no file access could still read any file on the machine, including the gateway's own configuration. Threads that have not disabled file reading are unaffected.

## v1.0.2

Patch release. Diagrams render again, and the web-app agent kit gains a supported way to give an app's visitors their own model.

- Mermaid diagrams now render, both in the gateway's chat page and in web-app agent panels. v1.0.1 withdrew them because the diagram library could not load in a browser without a bundler; it now ships with the gateway and is resolved through an import map that each page declares. A page that does not declare the map shows the diagram's source text instead — never an error box.
- The kit ships example thread configs and a one-command applier, so an app's per-visitor threads can run a cheaper model and a tighter prompt than the thread you use to work on the app. Previously the kit shipped no thread config at all, so every visitor inherited the management thread's model — a costly default that looks fine until an app has more than one visitor.
- Larger package: the vendored diagram library adds roughly a megabyte to the download. Pages that never render a diagram do not fetch it, so page-load size is unchanged.

## v1.0.1

Patch release. Fixes what v1.0.0 shipped incomplete in the web-app agent kit.

- The kit's shared block renderer is now included in the package. In v1.0.0 the kit's instructions pointed at a file the release did not contain, so app panels silently fell back to plain text.
- Assets are now versioned per serve, so a gateway or app update reaches browsers on the next load. Previously a CDN could keep serving stale scripts for hours — the origin's `no-cache` header was not enough on its own.
- Rich blocks written inside code fences are no longer swallowed by the renderer, and a block that cannot be rendered now shows its original content instead of an empty box.
- Mermaid diagrams are no longer offered in app widgets: they could not load in that context and rendered an error message. Charts, tables, and the other block types are unaffected.

## v1.0.0

First stable release.

- Cumulus is now licensed by Lucky Draw LLC: free for evaluation, development, testing, and personal use; production and commercial use require a paid license (see LICENSE).
- License keys with demo mode — unlicensed gateways run fully featured, capped at 5 visitor threads per app namespace.
- Web-app agent starter kit in `examples/web-app-agent`: a complete runnable app showing session-gated key delivery, device threads, in-page agent commands, and the confirm-gated risk tiers.
- Gateway settings redesigned: one default-model control, provider-organized model catalog (Anthropic, OpenAI, HuggingFace), in-app credentials and license key — changes apply live, no restart.
