8000 open-duet/CHANGELOG.md at main · Shahrainis/open-duet · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
431 lines (357 loc) · 74.7 KB

CHANGELOG.md

File metadata and controls

431 lines (357 loc) · 74.7 KB

Changelog

Unreleased

Added

  • Add a review-first agent migration flow for pasted Grok Bot and Hermes Agent exports, common single-agent JSON, and supported OpenBot databases. Preview the name, title and instructions before import; the new agent uses the normal safe defaults. Connected accounts, credentials, skills, routines, files and chat history are not transferred.

Changed

  • Keep active rooms inside the chat workspace so conversation navigation stays visible. Center and widen the docked composer with the transcript; keep member controls, instructions and export/delete actions in a responsive details panel, with the ordered reply queue and routing reason alongside room work. Add keyboard-accessible @ suggestions and distinguish duplicate agent names in the member list, queue and speaker labels.
  • Replace the compact Team coordination dialog with a full-screen workspace for agents, delegated work and rooms. Group jobs by attention, running, waiting and finished; show who requested and owns each job, its delegation parent and timing, with existing approval, open and stop controls. Let Work filters wrap on narrow screens so every filter stays visible.

0.8.1 - 2026-11-03

Added

  • Put language pickers on sign-in and chat, use English as the default, and translate common sign-in, chat, sidebar, search, audio, planner, approvals, team and room controls across the supported languages. Decision-log summaries, health-scan details, other specialized screens and engine-supplied text can still contain English.

  • Add an easy-to-find language picker to first-run setup and translate its main guidance and starter-agent templates (67 messages) across all 12 non-English locales. Choosing a language no longer resets setup progress.

  • Translate 85 additional messages across all 12 non-English locales: selected General Settings and Planner copy, Mac permission guidance, agent/team/skill/memory help, and high-impact routine access and safety notices. Many controls in these specialized screens remain English.

  • Redo the Agents and Edit agent screens: compact agent rows with an overflow menu for less common actions, search, one banner for engines that need sign-in, an editor with sections, a sticky Save footer, unsaved-changes and discard confirmation, and compact per-app access controls. Move the Telegram connection from the sidebar into Settings under AI > Channels.

0.8.0 - 2026-11-03

Added

  • Settings tabs on the Approvals page now work with the arrow, Home and End keys, and Cmd/Ctrl+K in chat opens the command palette instead of jumping to the sidebar conversation search.
  • Make settings easier to reach and change: search every setting from the Settings page or Cmd/Ctrl+K and jump straight to it, open Settings with Cmd+, (Ctrl+, elsewhere) and from a new desktop application menu, group the settings pages under You, AI and Safety, add a "Find agent settings" card, collapse engine cards that are not in use, and show one Save, Saved and Applies pattern across settings pages.
  • Add English, Spanish, and System default language choices for text covered by the interface catalog. System default follows Spanish browser locales; unsupported languages and missing translations use English, and older untranslated text stays in English. Frontend lint, the Webpack production build, frontend .mjs checks, desktop tests, and the SQLite backend suite passed; the full PostgreSQL suite, live-browser, and packaged-app checks remain unqualified.
  • Add German, French, Italian, Japanese, Korean, Dutch, Polish, Brazilian Portuguese, Russian, Turkish, and Simplified Chinese language choices. System default follows any supported browser language; dates and numbers use browser locale formatting. Text outside the interface catalog remains in English. Live-browser and packaged-app behavior have not been run.
  • Open the files an agent made from the phone app. A pairing code can now be made with Chat and files access (a new files scope; --scope files on the CLI): the phone lists a conversation's workspace, previews text and images, and shares or saves any file. A chat-only phone is unchanged and still cannot reach workspace files; the folder zip stays admin-only. Automated tests and lint pass; not yet run on a device.
  • Add an owner-selected Uninterrupted access setting for a routine. Scheduled and manual runs can use the agent's configured app accounts, granted marketplace tools and attached isolated computer without per-call approval. Each run records its starting capability ceilings and rechecks current permission before calls. Ordinary chats retain their own approval behavior. Real AppSumo research and updated installed-package qualification remain open.

Fixed

  • Fix the phone app's QR scanner in the Android release build. The build's code shrinking broke the scanner's setup, so the camera showed but never read a code. Keep rules for ML Kit are now set in app.json. Checked on an Android phone with a signed release build.

Changed

  • Stop the agent browser announcing itself as HeadlessChrome: its user agent, client hints and navigator.userAgentData now agree and name Chrome, with the real version and platform. Add a fixed notice when a page shows a visible CAPTCHA frame or a "Just a moment" or "Access denied" page, so the agent stops and asks the person to take control instead of retrying.
  • Remove the server-wide routine connected-app switch. Explicitly granted scheduled and manual routine actions are eligible immediately, subject to current agent permissions, active accounts and existing run restrictions.
  • Require Codex to initialize a routine's exact connected-app tools before its turn starts, waiting up to 10 seconds so missing tools cannot be mistaken for unchecked access. Name the authorized tools in turn instructions and direct Codex to call a matching tool before reporting app access unavailable.

0.7.0 - 2026-10-30

Added

  • Add a remote MCP Marketplace with Community and Tested by Open Dots sections, GitHub PR catalog validation, separately stored bearer credentials, exact per-agent tool grants and one-time call approvals. The source implementation is present; automated, live-service and packaged-app qualification remain.
  • Add a Desktop-only Local/Cloud computer choice. Cloud uses a Boat API key encrypted in the local Desktop profile; the local backend sends it only to Boat's API over HTTPS, not to the agent or cloud computer. The cloud machine has internet access, separate storage, a one-hour archive TTL and an asynchronous deletion lifecycle. The frontend production build passed; backend behavior tests, live-provider and packaged-app qualification remain open.

Fixed

  • Fix Today / Needs You refresh failing while serializing Sentinel issue rows.

  • Show readable, privacy-safe summaries for structured health-scan errors instead of placeholder punctuation.

  • Redact secret-like text from background memory capture prompts before they reach the agent's own engine, and use directly matched topic aliases as a bounded, on-device bridge when retrieving related conversation passages.

  • Recover cloud-computer removal when Boat has already deleted the sandbox or no longer retains its deletion-operation record, instead of leaving the agent blocked in deleting. Cloud desktop screenshots now transfer through Boat's file API and try the available X display/capture tools rather than sending base64 image data through command output.

0.6.0 - 2026-10-29

Added

  • Let each Claude or Codex agent use only the specific connected accounts its owner selects for each app. The backend validates account IDs against active connections and checks the agent's current grant on every call, including generic actions, routines and Today reads. Existing agents start with no account access until their owner selects it.

  • Add an Apps page (sidebar → Apps) that lists Composio's own catalog of apps, with search, categories, a Connected section, what agents can do with each app, connecting and disconnecting, and the Composio key. Without a key it shows a short starter list and says so. Connecting an app does not give any agent access; that is still chosen per agent. Connected apps moved out of Settings.

  • Let Claude and Codex agents use any connected app, not only GitHub, G 8BD0 mail and Google Calendar, through two tools: one finds an app's actions and one runs one. No action of these apps is treated as a read, so you approve every call on a card showing the app, account, action and exact arguments. Nothing that runs code, a shell or a raw request is reachable, the three apps with their own tools stay limited to them, and a changed action, a disconnected account or lowered agent access cancels a waiting call. Checked with a fake Composio that follows Composio's published API description; not yet run against the real Composio.

  • Add two Google Calendar tools: find events by words (read) and create one timed event (change, approved each time). A created event has no guests and no video meeting, so it emails nobody. Checked with unit tests against Composio's published parameters; not run against a live calendar yet.

  • Add opt-in quiet routine results with durable outcomes and visible incomplete source checks. Quieting requires explicit outcome reporting and complete connected-app read receipts; empty output alone stays visible. Not run: real-provider and packaged-app qualification.

  • Add confined routine reply drafts for one message read in that run, with a server-derived recipient and one durable draft attempt per run. Available only on the supported Claude route after routine-app safety enablement; other write grants cannot be combined with this mode. Not run: live Gmail, Linux safety and packaged-app qualification.

  • Add bounded audio uploads and direct audio URL import with explicit OpenAI transcription consent, transcript segments and source playback. Transcript review turns cannot use tools. Requires configured transcription credentials and ffmpeg/ffprobe. Not run: real transcription, summary quality and packaged media dependency qualification.

  • Add read-only Gmail and primary-calendar source records to Today, with links, check timestamps, partial coverage and unavailable-source labels. Not run: live-account and installed-app qualification.

  • Add off-by-default routine grants for exact app reads, Gmail email sending, GitHub branch creation and file commits, bound to connected accounts. Includes run snapshots, revocation checks, durable write reservations and action history. Execution requires explicit server enablement after safety qualification; webhooks, retries and agent computers remain excluded.

  • Add a phone app (mobile/, Expo and React Native) that pairs with your own server by QR code or code as a chat-only device. It lists and starts conversations, shows answers as they are written (as Markdown, including tables that scroll sideways when wide), attaches photos and text files, follows the phone's dark or light setting, starts a conversation from one of your agents, searches your conversations (a page at a time, opening one at the matching message), reads and posts to your rooms (tagging agents, stopping them and answering their approvals), stops a running answer, and asks you to allow or deny tool calls, showing every input of the call. The Devices screen now shows a second QR code for the app when a chat-only code is created. Release builds connect over HTTPS only; there are no push notifications and no store release. Checked: 216 Jest tests, lint, typecheck, a bundle export for both platforms, the mobile CI job on GitHub Actions, the app's networking code against a local backend, and an Android emulator run in Expo Go (pairing by code and by QR, HTTPS with a valid certificate and refusal of an untrusted one, chat, an approval from a real Claude turn, and Stop). The newest features (Markdown, attachments, dark theme, agent picker, rooms) were checked by tests and against a local backend. A release APK was then installed on a Galaxy S23 Ultra: pairing over HTTPS and chat worked, and after fixes for the system bar and the keyboard the message box and approval buttons stay on screen. Scanning the QR code with that phone did not work yet; typing the server and code does. Not run: iOS, a custom link opening the app, and the newest features beyond chat on a screen. See the phone app guide.

Changed

  • Settings is now four pages with a list beside them: General, Engines, Approvals, and Account & privacy. Links that asked you to sign in to an engine now open Engines, and the desktop app accepts engine sign-in, the path picker and permission actions only from the page that hosts them.

0.5.1 - 2026-10-28

Changed

  • Desktop sign-in no longer asks for an invitation code; anyone can create an account with an email code.

0.5.0 - 2026-10-28

Added

  • Desktop: "Reset this profile" (also offered when a different account is linked) and "Delete my account". Both need a typed confirmation, refuse while an agent task runs, and erase local agents, conversations, settings and workspace files before unlinking; account deletion first deletes the cloud account and changes nothing locally if that fails. Sign-out never erases data.

  • Export your agents, conversations and attachment names as a ZIP from Settings ("Export my history"). Credentials, the account sign-in and attached files are not included; import is not available yet.

  • Guide desktop and browser users through engine choice, a purposeful first agent and an editable first-request draft, with saved progress, retry-safe completion and optional Mac permission checks. Desktop also gains email-code app-account sign-in with encrypted sessions and account-bound local profiles. Supported desktop engines can be installed into staged app-managed releases. Real email delivery, provider calls, actual runtime downloads, native permission dialogs, Windows installation, packaged releases and production account-service deployment were not run. See first-run setup.

  • Allow existing Codex agents to use the same per-agent GitHub, Gmail and Google Calendar Composio grants as Claude agents. Codex mounts a scoped MCP server per turn and routes tool requests through the existing one-use approval gate. The Codex live-provider flow and Gmail calls have not been qualified.

  • Navigate saved skill suggestions in chat with Up/Down and choose one with Enter, Tab or a click. Escape keeps the draft, disabled/already chosen skills are hidden, and the three-skill limit is enforced before selection. Choosing a skill sends nothing and leaves tool approvals unchanged. Real-provider, Windows and packaged-app skill menu checks were not run.

  • Check available disk space before starting an installer build and explain which directory needs more room. Builds require at least 2 GiB available on every output/temporary volume; larger builds may need more. Installer creation, installation, updates and Windows execution were not run for this change.

  • Require the real Linux filesystem and network sandbox checks in backend CI on Ubuntu 22.04, with bubblewrap installed and skipped checks treated as failures. The new CI job and real Linux enforcement were not run locally on this macOS host; provider sessions and stop/orphan cleanup still need separate qualification.

  • Sign out of the shared Claude or Codex CLI account from Settings after confirmation, then sign in again to switch accounts. Account removal refuses pending sign-in and active or starting conversations, bounds CLI execution, and verifies sign-out without exposing provider output. Real-account, Windows and packaged-app switching were not run.

  • Add a real Linux network sandbox integration check for host loopback denial, private-destination proxy refusal and gate cleanup. Linux enforcement was not run on this macOS host; the check requires bubblewrap and namespace support.

  • Let an existing Claude agent read connected GitHub, Gmail and Google Calendar accounts after you allow each app. Gmail can also save a plain-text draft reply in an existing thread; sending and extra recipients are not exposed. GitHub changes and Gmail drafts each require a one-use approval card. Answers are trimmed to a fixed field allowlist and marked as third-party text. Connected-app tools remain unavailable to other engines, agents with a computer, routines and turns started from outside. Existing manual checks on Windows covered GitHub only. The desktop static UI build and source-checkout Mac app launch succeeded, but no Gmail or Calendar call was made in that app. A packaged installer, Linux sandbox and provider behavior were not qualified. The Gmail approval shows the chosen recipient but the current chat action does not verify it against the thread sender. Until a credential broker exists, the saved Composio key is visible to local agent shell commands. Tests for this increment were not run.

  • Connect GitHub, Gmail and Google Calendar from Settings using your own Composio project key. The key is stored encrypted and is never shown again; only the owner can manage it, and paired chat devices are refused. Scoped-key guidance names the required access because Composio may report missing permissions as an invalid key. Existing manual connection checks covered GitHub on Windows; Gmail and Calendar connections, grant revocation at GitHub, packaged apps, macOS and Linux were not run. See connected apps.

  • Sign in to Claude from Settings using the installed CLI's browser link and pasted authorization code. The starting owner session controls the bounded flow; existing accounts are preserved and codes are submitted once. Real-account, Windows and packaged-app sign-in were not run.

  • Run an isolated desktop source smoke check on macOS, Windows or Linux, and check unpacked Windows/macOS apps with the packaged smoke command. Both use disposable profiles; failed packaged checks now stop their launched processes. The macOS arm64 source window passed startup, access checks and restart. Installed artifacts and Windows/Linux execution were not run.

  • A version-tagged server image workflow verifies Linux amd64 packaging before publishing the tested image to GHCR. A Compose overlay and digest-pinned guide support installation without a local build. Registry publication/pull, ARM64, real providers and VPS/TLS operation were not run.

  • Start Codex device-code sign-in from Settings, with a bounded process and status/code access restricted to the starting owner session. Existing working accounts are preserved. Real-account and packaged-app sign-in were not run; account switching qualification remains open.

  • Show when a conversation is using a shared screen and require a fresh screenshot before coordinate input after tracked control changes. Refused inputs are never queued or replayed. Real-provider, Docker desktop and installed-app handoff checks were not run.

  • Save as skill now prepares a bounded, redacted draft from a successful chat turn's request and result, with source provenance and editable workflow sections. Explicit reviewed saves create a new disabled skill; revision-bound Undo refuses later edits. Existing skill selection invokes it only when explicitly chosen. SQLite/scripted-engine and cross-worker file-lock tests cover the workflow; real-provider, Windows and packaged UI qualification remain open.

  • Edit an existing room's roster while keeping conversation and work history. Removal skips queued replies, refuses members whose work is still settling, and records a room notice. Re-adding reuses history without replaying old work; stale concurrent roster edits are refused.

  • Routine changes made through chat have dedicated result cards and owner-only Undo. Undo refuses stale settings or executions and restores definitions paused, without replaying runs or restoring webhook grants/history.

  • A source-linked local Today brief extends the existing Needs you card with questions, routine outcomes, durable engine problems and due tasks. Calendar and Gmail remain unavailable; empty and duplicate updates stay quiet.

  • Separate opt-in Linux network isolation preview: private network namespace, per-turn UNIX-socket bridge and DNS-pinned public HTTP(S)-only gate. Real Linux and provider qualification remain open.

Fixed

  • Resolve the Python interpreter's real path before launching the Linux network sandbox bridge. Custom Python runtimes still need an explicit read-only grant; provider sessions, stop/timeouts and orphan cleanup were not run.

  • Concurrent Today refreshes and first visits serialize their state and publish one card and notification. A failed publication rolls back its new chat too.

  • Routine writers and manual run admission reload the locked definition, so concurrent Run now requests admit one run and a request following Undo uses the restored settings instead of a stale prompt.

  • Room reply completion locks the room before its dispatch, matching Stop and roster edits, and rechecks the dispatch after waiting. This avoids a PostgreSQL deadlock when a reply finishes during a concurrent room change.

  • Delegation shutdown gives an active scheduler transaction time to finish and stops additional passes before cancelling it, reducing SQLite lock races.

  • Repeated Undo reports its saved state without accessing expired database attributes after rollback.

v0.4.1 (2026-10-25)

Added

  • Google Antigravity engine with isolated personal-account sign-in and discovered models.
  • Explicit agent execution and scheduling from planner tasks.
  • Opt-in Linux turn sandbox preview isolates filesystem mounts, processes and temporary files with bubblewrap and refuses launch when checks fail. Network isolation and real Linux/provider lifecycle qualification remain open.
  • Publish an engine/platform capability matrix with separate source, automated, live-provider and packaged-release gates; correct stale Codex MCP and browser support descriptions.
  • Routine webhooks accept an optional Idempotency-Key and persist seven-day receipts so concurrent or restarted sender retries return the original run without executing it again. Reusing a key with changed content is rejected.

Fixed

  • Child turns cancelled during startup stop before executing delegated work.
  • Notes Undo refuses stale journal entries and checks the current hash again during writes and deletions, preserving later or concurrent edits. Undo newer changes first to restore older versions safely.
  • Standalone Remember that <one fact> requests save journaled private agent notes before the engine answers, including Codex without a helper. Exact repeats are idempotent; simple first-person corrections supersede earlier notes and support Undo. Secret-looking facts and shared/externally started turns are excluded. Engines receive a success or failure receipt and guidance to distinguish saved notes from conversation recall. Source and fake-process SQLite tests cover this path; live-provider and packaged behavior remain unqualified.

v0.4.0 (2026-10-25)

Added

  • A persistent task planner with task boards and calendar views for work and deadlines.
  • Chat-based agent management and team lead settings for Claude and Codex.
  • The unsigned macOS installer omits native desktop preview; it remains available from a source checkout with the reviewed bundle staged.

Fixed

  • Computer agents now explain when browser tools are disabled.

v0.3.0 (2026-10-24)

Added

  • Desktop app updates: the installed app checks GitHub releases in the background and Settings → App updates shows a newer version, downloads the installer for macOS arm64 or Windows x64 into Downloads, checks its size and SHA-256 digest, and opens it on a click. Assisted rather than automatic because the installers are unsigned (in-place restart install needs signing first). Unit-tested with stubbed GitHub responses; not run against a real newer release or a packaged build.
  • Skills: an agent can keep saved, text-only instructions (skills/<name>/SKILL.md in its folder) and a message can name up to three of them; they are added before that one message as guidance, never grant permission or change approvals, and are never used unless named. Import preview, an agent-file round trip (imported skills arrive off), secret redaction and link refusal are included. Tested with a scripted engine on SQLite; a real model following a skill and PostgreSQL were not run.
  • Templates: Agents → Templates… opens a searchable, categorised gallery of 16 ready-made roles from a bundled catalog (GET /templates, no model call). Add fills the new-agent form for review; nothing is created or granted until Save, and engine, model, folder and approval mode stay your choice. Suggested skills are saved after the agent exists and a failure never undoes it. Roles that need email or community connections are listed as unavailable. Not run: a real browser pass, phone-width check, or the skills save against a real skills backend.
  • macOS desktop preview: named Claude agents can opt into native screen and accessibility tools, with per-action approvals, one-turn ownership, takeover and history redaction. The driver is owned by Electron and loaded only from a verified offline bundle. Real OS permission/GUI interaction, a real model turn, Intel and packaged signing/offline startup have not been run.
  • Agent files: topic notes that are too large for the file (over 64 KB each, past 100, or past 600 KB together) are left out of an export and named in skipped; an import is checked against the same total, and each note it writes is an undoable History row by Imported agent file.
  • Gemini CLI and Cursor appear as ACP presets with their own connection fields, credential scope, bounded status/model discovery and pinned model selection. Signed-out turns give connection advice. Synthetic ACP tests cover the new hooks; real accounts and vendor approval behavior have not been verified.
  • Experimental OpenCode is available only through OPEN_DOTS_PRESET_ENGINES; its subprocess pins permission requests and disables project configuration. Real adversarial permission proof is still required before default enablement.
  • Server and desktop doctors include preset CLI guidance; the server guide documents API-key connections and optional Docker packages. Optional engine image builds were not run.
  • Engine Settings now builds connection forms from engine descriptors and groups ready engines first. Generic endpoint address templates cover Ollama, LM Studio, Groq, Cerebras, Mistral and OpenAI. Desktop setup detects a ready engine and offers advanced model selection. Tested with scripted engine responses; real preset accounts and packaged setup were not run.
  • OpenRouter readiness authenticates the saved key, lists up to 600 models with free labels, and chooses a free default only for a free-tier key. Its address is fixed to OpenRouter; proxy users must use the generic endpoint engine. Keys refused by the service no longer appear ready. Checked with a mock endpoint, including conversation fallback; a real OpenRouter account was not run.

v0.2.0 (2026-10-22)

Changed

  • Renamed to Open Duet. The product name shown in the app, the desktop messages, the installers' file names, the README and the docs is now Open Duet. Names that identify an install did not change, so nothing needs migrating: the OPEN_DOTS_* settings, the ~/.open-dots data folder, the desktop data folder (still Open Dots, which also keeps its encryption key readable), the opendots service account, the open-dots.service unit, Docker image and volume names, and the X-Open-Dots-Token header.
  • The repository moved to Anil-matcha/open-duet; the old address redirects. Clone commands in the README and the self-hosting guide use the new name.
  • Engine processes get an allow-listed environment. Claude, Codex and ACP engine processes (and the shell commands and tool servers they start) used to inherit the server's whole environment minus five names. They now get standard shell, OS, proxy and certificate names plus the engines' own settings and provid 10000 er keys; anything else is dropped. OPEN_DOTS_ENGINE_ENV adds names or PREFIX* patterns; the app's own secrets, database settings and OPEN_DOTS_* names can never be added.
  • The web UI and API now require sign-in (owner authentication). Every request outside the desktop app must come from the owner signed in with a passphrase (a session cookie), from a device paired with a one-time code (a bearer token), or, only if you enable LOOPBACK_TRUST, from this machine. On first start the page asks you to set the owner passphrase, which works only from the server itself; on a remote server run uv run python -m app.auth.cli set-passphrase in backend/. Scripts that called the API from this machine without a token now get 401 unless you pair them (python -m app.auth.cli pairing-code, then POST /api/v1/auth/pair) or set LOOPBACK_TRUST=programs (read the tradeoff in docs/AUTH.md). Requests whose Host is not in the new TRUSTED_HOSTS (default localhost,127.0.0.1,[::1]) are refused with 421; add your public name when self-hosting. The browser no longer decides who it is: the guest or Telegram id the web UI kept in localStorage moves to the server on the first sign-in and is then read from the session. The desktop app is unchanged. Run alembic upgrade head (it adds three tables).
  • Claude runs started by the app no longer let the CLI save its own memory notes under ~/.claude/projects/ (outside the workspace, without an approval card). The app sets CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 for the processes it starts; your own use of the CLI is unchanged.
  • The backend now checks every 15 seconds for conversation turns (see "Durable local conversations" below) whose process stopped, and interrupts the turns it is running when it shuts down cleanly. It does nothing unless threads are used.
  • Boat is now an optional provider. BOAT_API_KEY, HOOK_TOKEN and PERMISSION_HOOK_BASE_URL are no longer required to start the backend; scripts/dev.sh only requires the hook settings when a Boat key is set. Work that needs a Boat sandbox answers 503 "Boat is not configured" when the key is empty, and the permission hook endpoint rejects every request when HOOK_TOKEN is empty. Existing setups with a key are unchanged. Without a Boat key the app is chat-only unless you enable local tools (below): local Claude answers questions but cannot use schedules, Telegram tasks or GitHub push, so keep Boat configured if you need those.

Fixed

  • Returning to an engine with a native session after a fallback turn now supplies the missing exchanges as historical background from the durable transcript. The handoff survives backend restarts, includes partial fallback output, and clears after a successful original-engine turn or an explicit session reset.
  • A fallback engine that replays conversation history now uses its own context budget and token estimate, including when the original engine keeps a native session and does not compact replayed history.
  • Page titles say Open Dots instead of the old product name.
  • SIGTERM no longer hangs the backend while the web page is open. The server waited for every open connection before shutting down, and an event stream never closes by itself, so a stopped backend kept waiting with its turns still running. Event streams now end as soon as a stop signal arrives; shutdown stops the background jobs first, refuses new turns, kills each running turn's engine process group and marks the turn interrupted, and settles routine runs that followed those turns as interrupted with their notification (they used to stay running until the next start). Tested with real signals on the real backend process. See docs/RELIABILITY.md.
  • Starting a turn no longer hangs when an engine's status check hangs, or answers 500 when it raises: the check is bounded (45 seconds) and a broken engine is reported as unavailable (503 with the reason).
  • The PostgreSQL schema-equivalence test no longer reports the computer table as different: PostgreSQL turns a quoted integer default ('1000') into a bare 1000 and SQLite keeps the quotes; both now count as the same default.
  • Security: a newline (or a single &, ||, a subshell, $(...) or backticks) no longer hides a risky command behind a harmless one. The git risk classifier used by the sandbox permission hook only split on &&, ; and |, so git status followed by a newline and git push was classified as a read and a saved "always allow" for git status approved the push (issue #157). It now splits on every shell separator, finds git anywhere in each part (including sudo git, env X=1 git, bash -c '...'), and classifies by the riskiest verb. The local safety floor had the same flaw: echo hi, newline, rm -rf ~ read as one harmless command. A newline now starts a new command there too. Newlines inside quotes are unchanged.
  • On SQLite, a deleted conversation's id is no longer given to the next new conversation (delete conversation 4 and the next one used to be 4 again), so a saved /chat?thread=N link, the decision log's conversation filter and the sentinel's scan no longer end up pointing at a different conversation. The same holds for turn and event ids. Run alembic upgrade head (scripts/dev.sh and the desktop app do): revision 8d5f3a2b6c74 rebuilds the thread, turn, event and approval tables as AUTOINCREMENT in one transaction, keeping every row, id, index and foreign key, and starts new conversation ids above any id the decision log still names. The desktop app copies the database aside first, as for every update. PostgreSQL never reused ids; its schema is unchanged and the migration does nothing there. Tested on SQLite and PostgreSQL. See docs/SETUP.md.
  • docs/SETUP.md no longer says an agent's new instructions and notes apply to its next message in a conversation already started: the Claude CLI keeps a resumed session's system prompt (checked with Claude Code 2.1.302), so they reach conversations started after the change; model and approval mode do apply to the next message. It also says that Plan only still lets the CLI save its plan file in ~/.claude/plans.
  • The Approvals panel's Add a rule form no longer squeezes the command box to a sliver beside a full-width "Exactly / Starts with" menu; the Decisions and Health filters get the widths they ask for too.
  • On SQLite, Always allow this command no longer fails with "database is locked" and answers "allowed this once only". Saving (and removing) a rule now takes the write lock before it reads, instead of upgrading a read whose snapshot went stale while the turn the answer had just woken up was writing its events.
  • The PostgreSQL schema-equivalence test no longer reports the agent table as different: PostgreSQL reports string defaults with a cast ('ask'::character varying) and SQLite reports boolean defaults as 1/0; both are now treated as the same default. The agent table and thread.agent_id have their own equivalence check.
  • The page no longer dead-ends when Claude is not connected in a Boat sandbox. Task history is always readable and expandable (only sending new tasks and replies needs Claude connected; with Boat off the new-task and reply boxes are hidden), and GitHub can be connected as soon as the sandbox exists instead of waiting for the Claude login. With Boat off (BOAT_API_KEY empty) the Connect Claude, GitHub and submit steps are replaced by a short explanation, and the history stays visible. New GET /api/v1/providers reports whether Boat is on (never a key or URL); an older backend without it is treated as Boat on.

Added

  • OpenRouter engine preset (see docs/ENGINES.md): a new openrouter engine on top of the OpenAI-compatible one, with its own encrypted configuration so it can sit beside a local endpoint. Only an API key is needed (the address defaults to https://openrouter.ai/api/v1); model ids are OpenRouter's (openai/gpt-4o-mini); requests to openrouter.ai carry OpenRouter's optional HTTP-Referer/X-Title app-attribution headers (used for its public app rankings; never sent to another address). Settings → Engines shows it. Chat only. Tested against a mock endpoint, not the real service.

  • Workspace files (F08): download a folder, or the whole workspace, as a zip. GET /api/v1/threads/{id}/files/zip?path= (admin scope, like the rest of the file browser) applies the same confinement to every file it adds: secret files and folders, links out of the workspace, special files and names unsafe inside a zip (backslashes, drive prefixes, control or bidi characters, invalid UTF-8; counted in X-Skipped-Unsafe-Names) are left out, folder links are not followed, each file is opened with O_NOFOLLOW and an inode re-check. At most 2000 files and 200 MB before compression (413 too_large otherwise); the temporary zip is deleted after it is sent. A Download zip button in the Files panel. See docs/EVERYDAY.md.

  • Routines can retry a failed run (see docs/ROUTINES.md). retry_limit (0 to 3, default 0, off) and retry_delay_minutes (1 to 1440, default 5) are set per routine, in the API, in export/import and under "Limits and approvals" in the editor. A failed run (an engine failure, or a start that failed because the engine was unavailable, the workspace was not usable or an unexpected error occurred) is queued again as a new run with trigger retry, its attempt and the retry_of run it repeats. A completed, stopped, run-limited or interrupted run, and a start that failed for a configuration reason (agent gone, archived or unavailable, unknown engine, invalid model, local execution off), is never retried. A scheduled occurrence that falls due replaces a waiting retry, and a retry still queued at a restart and past the catch-up window is recorded as missed. A unique index on routine_run.retry_of keeps a failed run to one retry, and the usual conditional claim keeps it to one start. Run alembic upgrade head: revision c8d2e5a9f314 adds the four columns and the index (no table rebuild).

  • Self-hosting on a server you own (experimental, one owner; see docs/SELF-HOSTING.md, which lists what was run and what was not). python -m app.server runs the backend and the exported web pages in one process (OPEN_DOTS_HOST, OPEN_DOTS_PORT, OPEN_DOTS_UI_DIR); the pages are public so /login and /pair can load, while the API, /hooks/* and /docs keep their sign-in rules. It listens on loopback and refuses any other address unless OPEN_DOTS_ALLOW_PUBLIC_BIND=1 is set. Commands: migrate creates or updates the database first (a SQLite file is copied aside before a pending change; a file this app did not make or a wrong encryption key is refused untouched, and the server will not start on a database that is not ready), doctor checks the machine and changes nothing, backup and restore make and unpack one archive of the database, attachments, decision log, notes and workspace (not the encryption key or the engines' sign-ins), and service writes a systemd unit. A Docker image and compose.yaml run it in a container with its data in a volume, and deploy/proxy/ holds nginx and Caddy overlays with TLS. TRUSTED_PROXIES names the addresses (besides loopback) whose X-Forwarded-For and X-Forwarded-Proto are believed; nothing else is, a network-wide or * entry is refused, and FORWARDED_ALLOW_IPS is ignored. OPEN_DOTS_PUBLIC_URL is one setting for the address people use: its host name is trusted and the pairing command prints a full link. The log warns once when a visit to the public name arrives without X-Forwarded-For or X-Forwarded-Proto from the reverse proxy. Engine programs installed in the service account's home are found under a service. node scripts/build-ui.js builds the pages for the desktop app and for a server alike. The Routines panel now shows a full webhook address when the page is served by the backend itself. Not run on a real server: see the guide.

  • Reliability and limits (see docs/RELIABILITY.md and docs/ENGINES.md). One routine scheduler per PostgreSQL database: the backend holding a session-level advisory lock runs it; others stand by, queue Run now and webhook runs for it, and take over within one pass after it stops (clean stop or kill -9); startup recovery on PostgreSQL leaves runs whose turn still heartbeats alone. Orphan sweep: engine process groups are recorded in ENGINE_PROCESS_DIR with start time, program and owner, and engines carry an environment marker; the next start kills what a crashed backend left (a recycled pid, a live backend's engines and a cleanly stopped backend's background processes are never touched). Replay classification: turns record engine_phase; turn.interrupted carries replay (before_accept, unknown, after_accept) and the page says whether the engine had the message; nothing is re-run. Limits: usage/rate limits and lack of credits reported by Claude (including its rate_limit_event reset time), Codex and OpenAI-compatible endpoints become engine_limited; GET /engines returns limit and the picker shows a "resets in" chip. Fallback engine: agents take fallback_provider/fallback_model; a turn the engine refused before accepting it moves to the fallback only when the fallback covers tools, approvals, images, the agent computer and a continuing conversation's earlier messages and is ready, with an engine.switched notice in the transcript; otherwise the turn fails with the reason and next step. Run alembic upgrade head: revision f3b8d1e6a274 adds agent.fallback_provider, agent.fallback_model and turn.engine_phase (nullable, no table rebuild). Tested on SQLite and PostgreSQL 14 (schema equivalence, migration round trip), two real backends on PostgreSQL, real processes for the sweep, and live with Claude CLI 2.1.302 on haiku behind a fake 429 endpoint.

  • Memory depth and context management (see docs/MEMORY.md). Topic notes: besides MEMORY.md, each agent keeps memory/<topic>.md notes (front matter title/aliases optional); the system prompt lists them as an index (at most 40 topics / 2,000 characters), never their bodies. Same safety as MEMORY.md: secrets redacted on save and on every load, atomic 0600 writes, 64 KB per note and 100 notes per agent, a hash check that refuses a save over a change made meanwhile (409 memory_changed), links never followed. GET/PUT/DELETE /api/v1/agents/{id}/memory/topics[/{name}] and a Topic notes tab under the agent's notes in the Agents panel. Recall before each turn: the person's message is matched (any content word; two needed for a message of five or more) against the agent's topic notes and its other conversations with a portable OR query on SQLite and PostgreSQL; up to 4 note and 4 conversation passages, at most 6,000 characters, go in front of the message (never the system prompt, so prompt caching stays stable) inside a block that says they are background found automatically and grant no permission; stored as a memory.recalled event and shown as a Recalled chip that opens to show every passage. Per-agent recall_enabled (on by default; off with notes off). Context compaction for engines that replay the transcript (OpenAI-compatible endpoints, and ACP agents that cannot reload their session): when the estimated replay (per-engine characters-per-token) reaches the budget (24,000 tokens, or 80% of a new optional context_tokens on the endpoint's config), everything before the last 4 exchanges is folded into a deterministic excerpt summary (no model call), stored as a context.compacted event so later turns replay the same summary, and the transcript shows Earlier messages summarized with the summary behind a button. Claude and Codex keep using their CLI's own compaction. Journal with undo: every change made through the app to an agent's notes is recorded outside the agent's folder with a diff and the earlier text; History shows it and Undo puts a file back (an undo can be undone). Proposed notes: once a conversation has been quiet for two minutes, lasting statements in the person's own words ("remember that…", "I prefer…") are queued as proposals; nothing is written until you Accept (journaled) or Reject (never proposed again). The OpenAI-compatible engine now receives the turn's prompt for the current message (so attached text files and recalled passages reach it). Run alembic upgrade head: revision e7a91c4d2b58 adds agent.recall_enabled. Tested on SQLite and PostgreSQL 14 (full suites and schema equivalence), and live (see docs/MEMORY.md).

  • Everyday features (see docs/EVERYDAY.md). Search: GET /api/v1/search?q= over conversation titles, what you wrote and what the agent answered; every word must appear in one title or message (case-insensitive, wildcards literal), newest first, with a ~160-character snippet and [start, length] highlights; filters agent_id, since/until, include_archived, limit. One portable query on SQLite and PostgreSQL (JSON element access, no FTS index). A search box in the conversation list. Attachments: POST /api/v1/threads/{id}/attachments (multipart) and attachment_ids on POST .../turns (up to 8); PNG/JPEG/GIF/WebP up to 10 MB (checked against the file's signature) and UTF-8 text files up to 1 MB; 200 MB per conversation; stored outside every workspace under a fresh UUID name with a sanitised display name, removed when the conversation is deleted. Text files are added to the prompt; images go to Claude as stream-json image blocks (chat-only runs switch to stream-json input for that turn); any engine whose capabilities say no images refuses them before the turn with 422 images_unsupported. The turn.started event lists the attachments (never their bytes); the transcript shows thumbnails and file chips; the composer takes paste, drop and a file picker. Claude's capability flag images is now true. Workspace files (F08): GET /api/v1/threads/{id}/files, /files/content, /files/raw and /files/download: relative paths only, canonical containment, O_NOFOLLOW with an inode re-check, symlinks out of the workspace refused (and left out of listings), secret files (.env*, .git/, .ssh/, .aws/, keys and certificates) listed as hidden and never served, preview 256 KB, download 50 MB, Content-Disposition: attachment, nosniff, CSP: sandbox. A Files panel. Usage: a turn's terminal event now carries the usage its engine reported; Claude's cache read/write tokens are recorded; GET /api/v1/usage?month=YYYY-MM totals by conversation, engine/model and day, and GET /api/v1/usage.csv one row per turn with formula cells neutralised. Costs are only ever sums of reported costs: a group where nothing reported a cost shows "not reported", never 0. Per-turn usage in the transcript, a conversation total, a Usage panel. Errors and empty states: failed turns add a next step for known error codes, and empty lists, empty folders, empty months and no search results say what to do. Access: search and attachments are in the chat scope; workspace files and usage are admin-only; all need a signed-in session (401 otherwise) and cookie writes need the CSRF header. No migration. Tested on SQLite and PostgreSQL 14 (full suites), and live with the real Claude CLI 2.1.302 on haiku: an image turn with tools on and with tools off, a text-file turn, a search finding an earlier message, the file browser refusing .., an absolute path, a symlinked file and a symlinked folder out of the workspace and .env, a Codex conversation refusing an image, and the whole flow in the browser.

  • Reliability (see docs/RELIABILITY.md): one backend per SQLite database, through an operating-system lock next to the database file that ends with its process (a crash leaves no stale lock); a second server or desktop launch on the same data refuses to start and names the process that holds it. Turn watchdog: a turn whose engine does nothing for TURN_STALL_SECONDS (default 600, 0 off) while not waiting on an approval is stopped and fails with engine_stalled. Tests that run the real backend process (backend/tests/e2e/): shutdown with real signals, engine resilience with hanging and crashing engine programs, and features checked together under the real auth middleware (a routine on an agent with a computer waiting for an approval, the webhook ingress against the main app, chat-scope devices, approvals and the decision log on Claude, Codex and ACP). scripts/smoke-backend.py, a first-day end-to-end run (passphrase, sign-in, agent turn, approval, routine, second backend refused, SIGTERM, restart) with a fake claude, now a CI job.

  • Routines: local agents on a schedule, by webhook, or by hand, without Boat (see docs/ROUTINES.md). A routine runs a prompt as a new conversation of one named agent, through the same thread runtime and approval gate as a chat (the agent's engine, model, instructions, workspace and computer). Schedule kinds: cron (Sunday = 0, either-day-field semantics), chosen weekdays at a time, every N minutes (elapsed, from an anchor), once, and manual; IANA timezones; a spring-forward gap fires after the gap and a repeated autumn hour fires once; the editor shows a plain-language summary and the next three dates from the same code the scheduler uses (POST /api/v1/routines/preview). The scheduler is a 5-second background loop in the backend: a due occurrence later than its catch-up window (default 60 minutes, plus 2 minutes of slack) is recorded as missed; a backlog runs its newest occurrence once and records the rest as one missed run; an occurrence while the previous run is still working is skipped, never overlapped. Unattended runs never approve by themselves: a run uses the stricter of the routine's and the agent's approval mode, a request parks the run in needs_input with a notification, and nobody answering within the routine's approval wait (default 30 minutes) expires the request, which the engine receives as a denial; saved rules and the safety floor apply as in a chat. A run limit (default 30 minutes) counts active time only. Restart recovery: a run is marked running before its conversation is created, and at startup any run left running or waiting becomes interrupted and is never re-run. Run history (queued, running, needs_input, completed, failed, missed, skipped, interrupted) links each run to its conversation. Webhooks: POST /hooks/{id} with a bearer secret that is shown once, stored only as a SHA-256 hash and rotatable; checked before the body is read; bodies over ROUTINE_WEBHOOK_MAX_BYTES (64 KiB) are 413, non-UTF-8 415, a paused routine 409, more than 3 unfinished webhook runs 429; the body reaches the agent inside a delimited, escaped block marked as untrusted data. app/ingress.py is a separate app that serves only GET /health and POST /hooks/{id}, for exposing webhooks through a tunnel. Notifications: an in-app feed (/api/v1/notifications, unread count, mark read) for runs that need input, failed, were missed or interrupted, or completed, with a bell and unread badge in the Chat header, plus a copy to ROUTINE_TELEGRAM_CHAT_ID when it and TELEGRAM_BOT_TOKEN are set. Export leaves secrets out; import creates routines paused and without a webhook. The Chat page gets a Routines panel (create/edit with schedule picker and preview, run now, pause/resume, history with links to conversations, webhook URL and secret shown once, delete). ThreadRuntime.send_turn accepts a per-turn approval mode and approval timeout. The Boat schedules are unchanged. Run alembic upgrade head: revision b5e3c8a1d702 adds the routine, routine_run and notification tables and changes nothing that exists. Tested on SQLite and PostgreSQL 14 (including schema equivalence), with time-travel tests for cron, timezones and DST, missed runs, overlap, approval parking and timeout, the run limit, recovery and webhook auth; verified live with the real Claude CLI 2.1.302 on a haiku agent (every-minute routine, webhook through the main app and through the ingress, approval parking that timed out to a denial, and a backend killed mid-run) and in the browser.

  • Owner authentication and paired devices (see docs/AUTH.md). Owner: one passphrase per install, stored as a scrypt hash; set on first run from the server itself or with python -m app.auth.cli set-passphrase (which also replaces a forgotten one and signs browsers out). Sessions: od_session cookie, HttpOnly, SameSite=Lax, Secure over HTTPS (or behind a TLS proxy on the same machine), 30 days renewed on use up to 180; only SHA-256 digests of tokens are stored. CSRF: cookie-authenticated writes need X-CSRF-Token, derived from the session and handed to the page with it; sign-in routes accept JSON only. Pairing: Settings → Devices (or the CLI) opens an 8-character one-time code, valid 5 minutes, with a QR code of the pairing link (a small built-in encoder, no new dependency); the device's browser gets its own cookie, any other client a bearer token once. Codes are single use; 10 wrong codes from one address lock it out for 10 minutes, 10 wrong codes from anyone kill all open codes, and auth endpoints allow 30 requests a minute per address. Devices are admin (everything) or chat (conversations and approvals only; a default-deny route list). Devices list: every signed-in browser and paired device with scope, created and last seen, revocable at once. Host/Origin: TRUSTED_HOSTS allowlist against DNS rebinding, and an Origin must be the frontend's or the server's own. Loopback trust: LOOPBACK_TRUST=off|programs|all (default off); never applies when proxy headers are present. New endpoints: GET /api/v1/health, GET /api/v1/auth/status, POST /api/v1/auth/setup|login|logout, GET /api/v1/auth/session, POST /api/v1/auth/pairing-codes, POST /api/v1/auth/pair, GET /api/v1/auth/devices, DELETE /api/v1/auth/devices/{id}, PUT/DELETE /api/v1/auth/identity. Web UI: sign-in and first-run screens (/login), /pair, Devices and Sign out in Settings, redirects to sign-in when a session ends. Local execution now also accepts a signed-in or paired session (not only a browser on the machine). Tested on SQLite and PostgreSQL, and live against a running backend and the web UI.

  • Desktop first-run setup in Settings: engine/model readiness and sign-in help, workspace root, local tools and approval-mode guidance, optional starter agent and direct entry into Chat. Completion is a versioned database preference; skip preserves current preferences and replay avoids adding duplicate agents by default. Verified in an isolated macOS Electron launch, including a real Codex turn with the created agent.

  • npm run doctor in desktop/: read-only source prerequisite checks, actionable setup/build advice and CLI executable discovery, with --json output and explicit exit codes. It never starts engines or reads credentials; sign-in and configured endpoints are checked in Settings → Engines.

  • Engines beyond Claude (see docs/ENGINES.md): an adapter contract with capability flags and a registry (GET /api/v1/engines, PUT/DELETE /api/v1/engines/{id}/config) whose probes never hang or crash startup; a Codex adapter over codex app-server with the approval gate (tested against a fake Codex); a generic ACP agent adapter (tested against a fake ACP agent) and an OpenAI-compatible chat adapter (tested against a mock endpoint, including that the key never reaches events, logs or errors). Agents and threads validate provider and model against the registry; computers stay Claude-only (409 engine_without_computers). Engine/model controls now cover agents, new chats and idle conversation model changes. Settings → Engines works in the web and desktop UI, with status refresh, ACP command configuration and encrypted endpoint settings; blank keys are retained only at the same URL. Missing Claude does not block another ready engine. Existing threads retain their engine when an agent changes engine; an unset thread model follows its agent only on the same provider. Not built yet: model prices and context windows, quota-aware fallback, images and MCP for other engines.

  • Agent computers (experimental). An agent can be given its own isolated Docker container (POST /api/v1/agents/{id}/computer, or Computer in the Agents panel), and its turns then run their commands and file edits there instead of on this machine: the host's own tools are off for those turns and the only tools are the computer's exec, read_file, write_file and list_files, served by a small MCP server bound to the container. Every call goes through the approval gate (cards, saved command rules matched like Bash, the safety floor, approval modes, the decision log); reads are allowed without a card, writes too in Accept edits, and Plan only refuses writes and commands. On Allow the app writes a one-use permit for exactly that call, and the MCP server refuses any call without one. The container runs as your own non-root user with no capabilities, no new privileges, no published ports, CPU/memory/process limits and only the agent's workspace folder mounted at /workspace; the network is off by default (or Docker's default bridge). The default image is python:3.12-slim-bookworm, pinned by digest; downloading an image is always an explicit step, never part of a turn. Lifecycle: start (makes the container from a local image), stop, recreate, live limit changes, idle stop after 30 minutes, a removal that is finished later if Docker is down, and removal of this installation's orphaned containers. A computer that cannot be used fails the turn with computer_unavailable; it never falls back to the host. GET /api/v1/computers/health reports whether Docker answers and never fails. Agents and conversations show their computer; the chat header has a Computer chip, and computer calls carry a "computer" badge. Run alembic upgrade head: it adds the computer table and changes nothing that exists. Tested with a fake docker executable on SQLite and PostgreSQL, and with the real Claude CLI 2.1.302 (tool list and permission prompt); the real-Docker tests were skipped because Docker was not answering on the development machine. See docs/COMPUTERS.md.

  • Notes for named agents, starter notes, and an agent as one portable file. Notes: each agent keeps a Markdown file, MEMORY.md, in its own folder (agents/<id> under the workspace root, even when the agent works in a project folder). The start of it, at most 200 lines and then 24 KB, is added to every turn after the agent's instructions, marked as background that grants no permission, and the agent is told where the file is so it can keep it current (its edits ask for approval like any other). Anything that looks like a key, token, password or private key is replaced with [redacted] when the file is saved and again when it is loaded; the file is written atomically with owner-only permissions, and a link in its place is never followed. GET /api/v1/agents/{id}/memory returns the text, its hash and how much of it loads; PUT saves it, and with expected_hash refuses (409 memory_changed, with the current text) to overwrite a change the agent made meanwhile. A new memory_enabled switch (on by default) keeps the notes out of turns without deleting them. starter_notes on POST /api/v1/agents gives a new agent its first notes; a duplicate starts with none; deleting an agent deletes its notes. Export/import: GET /api/v1/agents/{id}/export[?include_notes=true] returns one .agent.json file with the name, title, description, instructions and, if asked, the notes; never the model, folder, approval mode, pin or conversations, and the response lists the parts in which something was redacted. POST /api/v1/agents/import reads only those fields from such a file (anything else in it is ignored), so an imported agent always starts on ask with the default model and its own folder; a name in use gets a number, and include_notes: false leaves the file's notes out. Chat page: the Agents panel gets Notes (editor with a gauge of what loads, amber from 80% and red once anything is cut, and a choice between the agent's version and yours on a conflict), Duplicate, Pin, Export and Import, and the edit form gets title, description, a notes switch and starter notes. Run alembic upgrade head: it adds agent.memory_enabled (default true) and changes nothing else. Tested on SQLite and PostgreSQL.

  • Approvals in the Chat page, and more of the approvals backend. Card: a shell command's card has "Always allow this command" (sends remember: true), then Undo; when it cannot be saved the card says it was allowed once only and why. A call a saved rule allowed says so, with "Stop always allowing". The header shows the conversation's approval mode when tools are on. Approvals panel (Chat header, and a section of desktop Settings): saved rules for this folder or all (add exact or "starts with", for one folder or every folder; remove), the decision log (newest first, filter by event and conversation, load older, Export CSV) and a health scan. Backend: the answer to remember returns rule_id or remember_error; approval.resolved carries by: "saved_rule" and rule_id for an automatic allow; GET /api/v1/approval-rules?workspace= lists the rules that apply in a folder; deleting a rule is logged as a new undone decision (?thread_id= names the conversation); decision entries record the approval mode; GET /api/v1/decisions/export.csv exports the log (same filters, redacted again, spreadsheet formulas neutralised). Sentinel: GET /api/v1/sentinel/scan?hours=24 and python -m app.services.sentinel [--hours N] [--json], a read-only scan of stored thread events and the decision log for retry loops, repeated denials, tool errors and failed answers; issues hold normalized titles and evidence ids, never tool output. Safety floor: diskutil eraseDisk/eraseVolume/reformat/zeroDisk, /dev/disk/by-* and /dev/md/* devices, /libx32 and /lib*, the function f { f|f& }; f fork bomb, and commands inside eval are now caught; a program named by a variable ($CMD, bash -c "$CMD", eval $X) is unreadable, so never auto-allowed. Fixed: rm --no-preserve-root / without -r raised an error in the floor reader instead of being read. No migration.

  • Named agents (F06, backend). An agent is a reusable setup: name, optional title and description, instructions, model, workspace and approval mode (ask, accept_edits or plan), and it can be pinned, archived, duplicated or deleted. New endpoints under /api/v1/agents (GET/POST, GET/PATCH/DELETE by id, POST .../duplicate), local access only like threads. Start a conversation from one with agent_id on POST /api/v1/threads: the agent's provider is used and its workspace is pinned for that conversation (an agent with no folder of its own gets agents/<id> under the default workspace root), and every message uses the agent's current instructions (added to the CLI's own system prompt), model and approval mode; a model set on the conversation wins over the agent's. Names are unique among live agents. Deleting an agent keeps its conversations, unlinked, and they carry on without its instructions; archive to keep them linked. Run alembic upgrade head (scripts/dev.sh does): it adds the agent table and thread.agent_id, and changes nothing that exists. Tested on SQLite.

  • Approval modes, saved command rules, a safety floor and a decision log for local Claude turns (backend). Modes ask (the default and today's behaviour), accept_edits (the CLI lets file edits in the workspace through, everything else still asks) and plan (read-only) map onto the Claude CLI's own --permission-mode; an unknown mode is refused before the CLI starts, and chat-only runs stay locked down. There is deliberately no "full access" mode. Always allow: answering an approval with {"decision": "allow", "remember": true} saves that exact Bash command as a rule for that conversation's workspace folder; GET/POST/DELETE /api/v1/approval-rules list, add (exact or word-prefix match, optionally tied to a folder) and remove rules. A prefix rule never matches a command with shell operators (;, &, |, redirections, backticks, $(). Commands containing a credential, control characters or anything on the floor cannot be saved, and at most 200 rules are kept. A matching rule answers the question at once (the approval is still recorded and shown). Safety floor: a never-list of commands no rule can allow: recursive delete of /, the home folder or a system directory, recursive chmod/chown of the same, formatting or overwriting a disk device, force pushes, and fork bombs; a command that cannot be read to the end (unbalanced quote, $(...), eval) is held too. Such a call is asked about on a card as usual. Decision log: every card shown, allow, deny, rule auto-allow, timeout and cancellation is appended, with secrets removed and the input cut to one summary line, to a monthly decisions-YYYY-MM.ndjson (mode 0600, in ~/.open-dots/decisions, DECISION_LOG_DIR, or the desktop data folder; months older than DECISION_LOG_RETENTION_DAYS, default 180, are deleted). GET /api/v1/decisions reads it, newest first, with a before cursor and event and thread_id filters. All of it is local access only. No migration. See docs/SETUP.md.

  • Chat page: answers are rendered as Markdown (headings, lists, tables, quotes, emphasis, inline code, and fenced code blocks with a Copy button). It is a small built-in parser with no new dependency: raw HTML is never rendered, only http, https and mailto links are clickable, and a half-written streaming answer renders safely. The earlier "Markdown: not yet" note no longer applies.

  • Chat page: an Agents panel (list, create, edit, archive; name, instructions, model with suggestions, workspace folder, and an approval mode of Ask first, Accept edits or Plan only with a plain-language description), an agent picker for new chats, and the agent's name in the conversation list and header. It uses GET/POST /api/v1/agents, GET/PATCH/DELETE /api/v1/agents/{id} and agent_id on POST /api/v1/threads; against a backend without them (404) the controls are hidden and the page works as before.

  • Desktop Settings: database-backed, versioned local Claude preferences with sensitive-change auditing, native file/folder Browse, per-turn apply rules and fail-closed validation. Conversations now pin their workspace; database upgrades back up existing history before adding the workspace and settings schema.

  • A desktop app, run from a source checkout (experimental, Windows verified; macOS and Linux not yet run): an Electron window around the local backend, for local Claude only. npm run app in desktop/ does whatever is missing (setup the first time, a build when the page is out of date) and then opens it; or run npm run setup, npm run build, then npm run start (npm run dev uses its own data folder); starting never installs or builds. It needs no .env: on first start it makes an encryption key and keeps it with the operating system's secret storage (never as plain text, never replaced if it cannot be read), creates its SQLite database in its data folder, copies the database aside before an update that changes it, and leaves the file whole when it stops. Only its own window can use the backend: a fresh random launch token is handed to the backend before it listens and added to every request the window makes, so a browser tab or another program that finds the address gets nothing; the address and port are chosen by the operating system on every start. The Chat page now also says when it is opened outside the app, and has a "Check again" button when Claude is missing or signed out. A new Open Dots data folder holds the database, backups, logs and workspace; see docs/SETUP.md. Source and headless installs read the same settings as before and are not affected.

  • Stop, and managing conversations, in the Chat page and the threads API. Stop replaces Send while Claude is answering: Claude is stopped, what it had written so far is kept and marked unfinished, a question waiting for your Allow is cancelled (nothing runs), tool steps that had not finished are marked, and the conversation carries on with the same session. POST /api/v1/threads/{id}/stop. Rename, archive and delete are in each conversation's menu in the list: renaming keeps its place in the list, archiving hides it (and "Show archived" brings it back, with Unarchive), deleting asks first and removes the conversation's messages, tool calls and approvals from this app's database (not a secure erase: see docs/SETUP.md). Archiving and deleting are refused while an answer is running (stop it first). PATCH /api/v1/threads/{id} (title, archived) and DELETE /api/v1/threads/{id}. Deleting does not remove Claude's own copy of the session, which the Claude CLI keeps in your .claude folder; that is said in the confirmation. See docs/SETUP.md.

  • A Chat page at /chat (link "Open chat" on the home page), the first interface for durable conversations. A list of conversations on the left; on the right, the conversation with answers appearing as Claude writes them, each tool call as a step you can open to see its exact input and output, and an approval card that shows exactly what will run and waits for Allow or Deny (nothing runs before). Reload the page or restart the backend and the whole conversation is still there, and the next message continues the same Claude session. If the backend stops while Claude is working, the page says so and that the message was not resumed or sent again; if Claude can no longer find a conversation's session, it explains and offers "Start a new session". Not yet: Markdown formatting (answers are plain text). It needs the same setup as Local Claude and shows what to change when it is off or not signed in. See docs/SETUP.md.

  • Durable local conversations ("threads", experimental, off by default). A thread keeps its messages, answers, tool calls and approvals in the database, so a conversation survives a page refresh and a backend restart, and each new message continues the same Claude session. New endpoints under /api/v1/threads, local access only and behind the same LOCAL_EXECUTION_ENABLED switch as local Claude: create and list threads, send a message, page stored events, a server-sent-events stream that a browser can resume with Last-Event-ID and receive exactly what it missed, and answer approvals. The Chat page (see the next entry) is its interface; the Local Claude card is unchanged. A turn that was running when the backend stopped is marked interrupted and is never run again. A conversation whose Claude session can no longer be found fails with session_resume_failed and must be reset explicitly with POST /api/v1/threads/{id}/session/reset; the app never silently starts a fresh session. Transcripts, including what Claude read, wrote and ran, are now stored in the database, and there is no way to set a retention period yet (conversations can be deleted one at a time, see the next entry). Run alembic upgrade head after updating (scripts/dev.sh does); it adds four tables and changes nothing that exists. Tested on SQLite; on PostgreSQL the migration applies but the behaviour has not been verified yet. See docs/SETUP.md.

  • Local Claude can use tools, with your approval (opt-in): set LOCAL_TOOLS_ENABLED=true. Claude can then read and write files in its workspace folder and run commands (Read, Write, Edit, Glob, Grep, Bash). The page shows each tool call as a step, and when the Claude CLI would ask a person it shows the exact input with Allow and Deny buttons; nothing runs until you allow it. No answer in LOCAL_APPROVAL_TIMEOUT_SECONDS (default 300), Deny, or the run ending is a refusal, and time spent waiting does not count towards LOCAL_TASK_TIMEOUT_SECONDS. New endpoints GET /api/v1/local/tasks/{id}/approvals and POST /api/v1/local/tasks/{id}/approvals/{approval_id} (local access only); GET /api/v1/local/status now reports tools_enabled. An allowed Bash call runs as you with no sandbox, the CLI decides which calls ask, and approvals and the timeline are in memory only; read SETUP.md first. Off by default: runs stay chat-only.

  • python -m app.db.copy_to_sqlite: copies an existing PostgreSQL database into a new SQLite file. PostgreSQL is only read (one read-only snapshot) and never changed; the copy is built under a temporary name, verified row by row (credentials must still decrypt with your TOKEN_ENCRYPTION_KEYS), and published without overwriting anything, so a failed copy leaves no file. It refuses while tasks are active or other connections are open (--allow-active overrides), supports --dry-run, and does not change DATABASE_BACKEND: switching to SQLite is a separate step. See docs/SETUP.md.

  • Experimental SQLite storage (opt-in, no Docker): set DATABASE_BACKEND=sqlite. PostgreSQL remains the default and nothing changes for existing setups. New SQLite files start from a frozen baseline of the current schema; models use dialect-aware types that leave the PostgreSQL schema unchanged. Concurrency was reworked for SQLite (permission-approval wait, conflicting approval answers, reply admission, schedule claiming, no database snapshot held across slow sandbox/CLI calls); PostgreSQL behaviour is unchanged. Keep PostgreSQL for real Boat sandboxes and multi-process setups until those are exercised on SQLite; see docs/SETUP.md.

  • Local Claude answers now stream into the web UI as Claude writes them (server-sent events, GET /api/v1/local/tasks/{id}/events). The saved task still decides the final outcome, a partial answer is kept if a run fails, and an empty answer is shown as such. The existing polling endpoints still work. Events live in memory for now (single backend process).

  • Local Claude (experimental, off by default): run chat-only prompts through your own signed-in claude CLI from the web UI. Only usable from the machine running the backend. See docs/SETUP.md.

Added: durable collaboration records

Teams, bounded job records and shared room queues now have durable database records. Existing agents remain outside a team until the person opts in; existing conversations retain their chat kind. The foundation was checked with SQLite schema migration tests and PostgreSQL DDL snapshots. Real Claude delegation, room members and PostgreSQL concurrent backends were not run for this slice.

0