Repository navigation
Releases: rends-east/reemoat
Release list
v0.13.0
Added
- An agent can send you a file. Ask for a file and the agent puts it in the conversation as a card with its name and size; a tap saves it, and an image is shown in place. It works for a file anywhere on the machine, not only one inside the session's folder, and the agent may send one unasked when what it made for you is a file. The copy is taken when it is sent, so it still opens after the agent changes or deletes the original. Up to 100 MB each; a session keeps the newest hundred. Claude Code, Codex and Grok send without asking permission first, as does Cursor; opencode sends too. Update the app before the machines, or the file is sent and an older app shows only the tool's name.
Changed
- Your messages sit in a softer bubble: lighter, closer to the page, with even corners, as in Claude Code.
- Agents are no longer handed tools that only work in their own terminal. Claude Code loses the ones that address its other sessions and its remote control, its feedback and onboarding cards, switching to a worktree of its own (the session's folder is the one Reemoat gave it), and publishing to claude.ai. Codex loses its plugin-install prompt. What each can do here is unchanged, and its context is a little smaller. Scheduling tools are left as they were.
- Switching agent messaging off no longer takes the Reemoat tools away entirely: sending you a file is not a message to another agent, so it stays.
- Subagents are listed under Background tasks, as Claude Code lists them: a running one in Agents with its kind, its time, its steps and what it is doing now, and a finished one under Completed beside finished shells. Claude now runs most subagents in the background, so they had never appeared there. Its card in the conversation says it is running in the background rather than showing a check mark the moment it started. The line at the foot counts them.
- Stop appears the moment you send. A session that had been quiet for a while kept Send on screen, with a spinner, while its agent was brought back, and only then turned into Stop; pressing Stop during that wait now stops the message once the machine has it.
- An agent's file sends say which file they read, so a relative path taken from the wrong folder shows where it went, and a file behind a folder the machine will not open is reported as that rather than as missing. Claude Code's permission prompt for sending a file, which appears only where your own Claude settings ask for one, is now left to you.
- File names in an agent's reply are plain text again, rather than download buttons. To get a file, ask the agent to send it: it arrives as a card that saves the copy it sent.
- Claude's effort says what level it really runs at. The choice that was called "Adaptive" is "Auto", as Claude Code calls it, and shows the level Claude Code itself picks for the current model: "Auto · Medium" on Opus 5.5, "Auto · High" on Fable, "Auto · Xhigh" on Opus 4.7. It follows the model when you switch, and nothing is pinned. The level appears once the machine is updated; until then the choice reads "Auto".
Fixed
-
A table in an agent's reply squeezed its short columns to a letter per line whenever another column held longer text. Short columns now keep their words whole, and a column of short values, like sizes or names, keeps each value on one line.
-
After the computer woke from sleep, "Connecting…" could stay on screen while everything worked, until the next wake. It now clears within seconds of the server answering again. Waking also no longer reconnects the same conversation two or three times over.
-
Stop could leave a session stuck for hours. When the agent froze and ignored Stop, the turn ended only after half a minute and the next message went to the same frozen agent, which froze again. Now an agent that has not stopped 15 seconds after Stop is restarted where the conversation left off, and waiting messages go to the new one — unless it has work running in the background, which a restart would end. Ending the session in those seconds ends it for good.
-
A message sent right after Stop appeared above "cancelled" with Waiting for the agent to finish under it. It now waits until the stop has gone through and appears after it.
-
Commands a stopped turn never ran still showed as running, with the spinner on their row. They now show as stopped.
-
The controls under the message box flickered when a quiet session woke up, showing the agent's own defaults for a moment — Manual, default effort — before your choices came back. They now keep your choices throughout. This needs the machine updated.
-
A file larger than about a megabyte could not be downloaded from a machine reached through the relay. Pressing a sent file's card, or a changed file's download, did nothing for two minutes and then failed. The file now arrives in pieces, and the card shows a spinner while it does. This needs the machine updated; against an older one the app downloads as before.
-
"working…" was a button into Background tasks in sessions with nothing running in the background. It is a button only while something is.
-
Stopping a session while it was copying a file the agent sent kept an invisible copy, and in a session already holding a hundred sent files it could drop one the conversation still showed. Nothing is kept now unless the card appears with it.
-
A file the agent sent from a network folder that stopped answering was tried again on every request, each one holding a thread; the folder is now remembered as not answering. A request waiting behind another file gives up when it is cancelled, rather than when the other finishes.
-
A long file name could be cut through the middle of an emoji, leaving the conversation and the stored copy with different names.
These builds are AGPL-3.0-only, and conveying a binary is a distribution.
The corresponding source for this release is the Source code archive on this
page and https://github.com/rends-east/reemoat/tree/v0.13.0.
v0.12.0
Added
- Cursor is the sixth agent. It has a tile on New session like Claude Code, runs on the model your Cursor account last chose, and offers its own modes (Agent, Plan, Ask), its models and each model's effort in the strip under the message box. Sign in with a Cursor API key under Settings → Machines, or with the sign-in page it prints; Install fetches it with Cursor's own installer, and the daily refresh keeps it current. Its questions, its plan approval and its to-do list are drawn as the other agents' are, a subagent's steps are nested under the step that started it, and a Cursor session comes back after a restart like any other. Cursor keeps one configuration for your terminal and this app: a model chosen here, or "Allow always" on one of its cards, is remembered in
~/.cursor/cli-config.jsonfor both. It edits files in the session's folder without asking, and asks before a shell command its allowlist does not name, as it does in a terminal. - Cursor can ask you a question with buttons. Cursor gives its model no way to ask one when an app drives it, so Reemoat hands Cursor sessions a question tool of its own. The question appears as the usual card, and the conversation shows it exactly as it does another agent's: the answered card, with no extra rows. Answer within a minute and the agent carries on in the same turn. Answer later — even after a restart — and it reaches the agent as your next message, which the conversation does not draw a second time. Skip tells the agent to carry on; ✕ dismisses the card. Reemoat allows that tool's own permission request itself, since the card is where you decide.
- Sessions have nicknames, and
@names one. New session gives every session a short nickname — filled in from a list, with a dice to deal another, or your own — and the list shows it as@miraunder what the session is about. Agents address each other by it. In the message box,@lists every session this one's agent can reach, on any of your machines; pick one and the agent is told which session you mean and how to message it, beside your words rather than in them. A nickname is unique on its machine, and sessions from before this release are given one when their daemon restarts.pnpm client new --nicknameandpnpm client nicknameset one from a terminal. - Agents can message each other, on one machine and across your machines. Every session can list the others and write to one (
send_message), whichever harness either runs and whichever of your machines it is on. Every message is acted on: it wakes a session that is idle and reaches one that is working inside its turn, and the answer comes back the same way, so nothing polls. A sender can also ask to be told once if the other session stops without answering. claude sessions no longer carry Claude Code's own session list, which never showed any of these. A message for a machine that is off is kept trying for a day. Messages between machines are encrypted end to end, as the app's are: the relay carries them without being able to read them. A message from another agent is drawn as one, never as your own, and a session stops taking work from agents after twenty turns in a row without a word from you. The app links your machines by itself. Machines of other people, or on another server, cannot be reached yet.REEMOAT_PEER_MESSAGES=offremoves the tools. - Agent messaging can be switched off: for every machine, for one machine, or for one conversation. Settings has a new Permissions section with one switch for all the machines you own. Each machine's settings have their own, which is kept while the first is off, and a session's menu has Agent messaging for that conversation alone. Off takes effect at once. A running agent's messaging tools answer that messaging is off, and messages still waiting to be delivered are dropped, with a line in the sender's conversation. The link between two machines is cut at the relay, even while one of them is offline. Switching back on reaches conversations started or resumed afterwards. A machine can also be isolated: its sessions still message each other, but nothing reaches them from your other machines and they reach none. A machine whose daemon predates this is cut from your other machines, but its own sessions can still message each other until it is updated.
REEMOAT_PEER_MESSAGES=offstill turns messaging off on a machine, whatever the switches say. - A dark theme. The menu has a Dark theme switch under everything else; the app stays light until you turn it on. The choice belongs to this device rather than an account: every account on it shares it, and signing out keeps it. Every colour was re-chosen for a dark background rather than inverted, and text, borders, the diff and the question card keep the contrast they have in the light theme. The page is dark from its first frame, and in the desktop app the window's title bar follows the switch too.
Changed
- Closing the window no longer quits the app, on macOS and Windows. The close button — and ⌘W on a Mac — puts Reemoat away instead, so a stray click never stops your agents. On a Mac it stays in the Dock, and a click on its icon brings the window back. On Windows it stays in the notification area: a click on its icon brings the window back, and opening the app again shows the one already running. To end it, quit: ⌘Q or the app menu on a Mac, Quit Reemoat on the icon's menu on Windows. Quitting still stops the daemons the app started. Linux is unchanged.
- A colour of its own. Send, the main button of every decision, the machine you are on, and everything that says a session is waiting on you — its dot, the counts on machines, the bell — are now lake teal, from the painting on reemoat.com. Everything else stays as calm as it was.
- Code blocks are highlighted in colour. Keywords, function names, strings, numbers and types each have their own colour, in both themes; comments stay grey.
- Code and links in colour. In what an agent writes, inline code is orange on a soft, borderless chip, as Claude Code marks it, and a link is blue. Both themes have their own shades, and the app's own links stay as they were.
- A waiting session keeps its line. A session waiting on you is still marked by its bold title and its dot, and the line under the title keeps its nickname, its agent and its machine instead of being replaced by the question's title. The icons on that line sit further apart, and the gap grows with the text on a phone.
- Tapping an answer answers. When an agent asks a question with one answer to pick, tapping it moves on to the next question, or sends it on the last; you no longer press Next or Submit after it. Choosing several answers, or writing your own, still ends with Next or Submit. Answers no longer shrink under your finger.
- Sessions can be reordered under All, by dragging, as they can inside a machine.
- Three clicks on the conversation's header select one thing, the folder or the branch under the pointer, instead of painting the whole header.
- On a phone, the answer you are writing comes to the front. Once you start typing your own answer to an agent's question, the ready-made answers fade back so your words are what you read; tap one and it is chosen as before.
- Folder headers no longer count the sessions waiting in them. The session's dot, the count on its machine and the bell still say so.
- Triple-clicking inline code selects all of it,
origin/devrather than the paragraph around it, ready to copy, as in Claude Code, and further clicks leave it selected. A double-click still selects a word. - The settings menu marks the screen you are on with a rounded highlight inset from its edges, rather than a band across the whole column. The plugin market's menu matches.
Security
- The key that lets an agent use the messaging tools is no longer on a command line. Claude Code was handed it in its arguments, where any other account on the same computer could read it and send messages as that conversation. It now travels in the agent's own environment, which only you can read; the other agents never had it on a command line. A key also stops working the moment its agent's process ends.
- A message from another agent can no longer pass as Claude Code's or Codex's own text. More of the tags those harnesses write are neutralised inside a message, and what another machine says when it refuses a message is checked, shortened and shown only as a quotation.
- Continuous deployment no longer trusts the deploy host's key on first use.
deploy/ci-deploy.shpins the newDEPLOY_KNOWN_HOSTSsecret withStrictHostKeyChecking=yesand refuses to deploy without it. Add the secret before the next dispatch: the host's keys asssh-keyscan -H <DEPLOY_HOST>prints them, checked against the host's own fingerprints.
Fixed
- A message between machines is delivered once, even across a restart. A retry that reached a machine after it restarted could be delivered a second time; delivered messages are now remembered on disk for a day. A message a shutdown cut off is held and retried rather than reported as ended.
- A bare name never reaches the wrong session. When one of your machines did not answer,
send_messageto a nickname shared with a session on it went to the one here; it now asks for the full address. A session on a newer machine whose state this one does not know is still listed. - Cursor never approves a plan by itself. An unreadable plan request, or one that arrived while a conversation was being reopened, was answered in a way Cursor reads as approval; it is now declined.
- Switching agent messaging off also stops a message that was being handed to a busy agent at that moment, a stopped conversation can no longer send messages, and a plugin answering a question no longer counts as you.
- **Removing a saved agent key asks firs...
v0.11.0
Added
- Several accounts in one app. The menu now opens on your face, your name and
the server the account is on; pressing the name lists every account on this
computer, the current one ringed, then Add account. Tapping another account
switches to it — on a Mac at once and exactly as you left it, with the work in the
account you left still running; on Windows, Linux and Android the app reloads
onto it. Add account signs in to another account, on the same server or
another one, while the others stay signed in — up to ten. - Every account gets this computer as a machine of its own: its own daemon,
database, sessions and working copies, running from when the app opens until it
quits whichever account is on screen, and using one of that account's machine
slots. The first account on a server keeps that server's folder; each further one
lives in~/.reemoat/servers/<server>@<user id>/. - The server step opens on the server the app was built for, greyed, with a
pencil beside it to change it. An app built without one opens on an empty box, as
before.
Removed
- The "Rent a machine" link. An instance whose control plane set
REEMOAT_CP_MACHINES_OFFER_URLdrew it under the one-line installer — on the
home screen of an empty fleet and in Settings → Machines. It is gone from the app
and fromGET /v1/instance, and the variable no longer does anything: a control
plane that still finds it in its environment says so once at startup, and the
line can be deleted. Adding a machine is unchanged — the one-line installer is
still on all three screens, and so is the sentence shown in its place when the
machine limit is reached. An app from an earlier release stops drawing the link
as soon as its control plane is updated.
Changed
- New session no longer installs or signs in to an agent. When nothing on a
machine can start, the screen says why in one sentence and offers one button,
Agent settings, which opens that machine's Agents list. Coming back returns
to New session with the folder and the chosen agent as they were. - The Agents list can finish the job. A row that is not installed, not signed
in or would not start now has Set up in its menu. It opens that
agent's own screen: install with the installer's output, then sign in. The
list's own Install, which ran with no output, is gone. - Settings → Account → Server address shows your account's server and no longer
changes it. Another server is another account: add it from the menu. A wrong
address typed while signing in is still corrected from ‹ Server on the
sign-in screen. - Signing out signs out of the account on screen and takes it off this
computer, stopping its daemon; the app moves to the account you used before it,
or to the sign-in for that server if it was the last. Signing in as that person
again later brings back the same device and the same machine. An account whose
session ends by itself stays in the menu, marked signed out, and its sign-in
screen offers a way back to your other accounts and a way to remove it. - Updating keeps you signed in. The first launch asks the server whose sign-in
this computer holds and moves it to that account; the device and the machine
beside it move only where the server confirms they are that account's. Going
back to an earlier release afterwards asks you to sign in again. ⚠ Where the
one-line installer set this computer up, update with its daemon running. If it
has not answered by the time the app asks, the app sets the computer up again as
a second machine, and the first — with its sessions — stays offline.
Fixed
- The computer the app runs on is called "local" and comes first in the machine
list. The machine strip, the desktop rail and New session named it by its
host name ("MacBook-Pro…") and sorted it among the others by name. On this
computer they now say "local" and list it first — from the moment the app
opens, before its daemon has started, and without changing back while the
daemon restarts — and New session picks it by default whenever it is reachable.
Everywhere else — your phone,
another computer, anybody you share it with — it keeps its real name, and
Settings → Machines still shows the real name with "this device" beside it, so it
can be renamed there as before. Another machine that is itself named "local",
such as one an earlier version of the app set up, is shown with its id added
("local-2405b5ea…") so only this computer reads "local". If you have dragged it
somewhere in the list, it stays where you put it; if you had rearranged the
machines before this release, it stays where it was until you move it. - The app can set a computer up for more than one server, and for more than one
account on each. Signed in to a second server it used to refuse ("This
computer could not be set up"), because the daemon settings in~/.reemoatnamed
the first. Each account now gets its own daemon, database, sessions and working
copies —~/.reemoatstays with the server itsdaemon.envnames, a server's
first account otherwise lives under~/.reemoat/servers/<server>/, and each
further account on a server under~/.reemoat/servers/<server>@<user id>/, on a
port the system picks. Every account that is set up has its daemon started when
the app opens, and all of them stop together when it quits. Switching accounts
interrupts nothing: the other account's daemon, its running turns and pending
approvals, and a phone's way to this computer through it stay up while the app
runs. A new account's daemon starts empty — no plugins, system keys, custom agents
or pasted keys of its own — while the agent CLIs' sign-ins are shared by every
account.REEMOAT_AGENT_UPDATES,REEMOAT_AGENT_SOURCEand
REEMOAT_AGENT_CHANNELin~/.reemoat/daemon.envdo not yet reach the daemons
for other servers and accounts. - A daemon running here for this server that the app did not start is no longer
met with silence. One whose machine is in your list is adopted as before; one
your account cannot see is named in the setup notice. - The app no longer creates a second machine for a daemon already running on
this computer with its settings file somewhere else (REEMOAT_ENV_FILE,
pnpm daemonfrom a checkout); it adopts it. - A daemon's clean stop no longer deletes another daemon's announcement. Every
daemon removeddaemon.jsonon its way out, whoever had written it; now only the
one that wrote it does. install.sh --uninstall --purgenames the desktop app's daemons for other
servers and accounts before deleting them, and--uninstalllists them among the
data it keeps.- The model chip read "Newer version availa…" after Claude Code moved its
opusalias. Claude Code 2.1.280 describes a conversation resumed on a model
an alias has since moved past with a notice instead of the model's name, and the
chip showed the start of that notice. It now shows the model the row names,
"Opus 5". The notice stays in the menu under that row, where it tells you to pick
Opus for Opus 5.5. - A Claude Code update the daemon did not install was picked up only ten minutes
later. When something other than the daemon updated the CLI (its own updater,
another daemon, ordeploy/deploy.sh), the new-agent screen kept naming the
previous build, and listing its models, for up to ten minutes. The daemon now
checks which file it would run each time it uses it, so the new build's version
and models appear the next time you open the new-agent screen. A session that is
asleep still shows its old list until it wakes. - The Android app would not install on a OnePlus 13 by tapping the APK. The
phone's own installer — OxygenOS, Android 16 — answered "App not installed as
package appears to be invalid", while the same file installed on a Pixel, and
overadbon that very phone. The APK carried only the signature Android has
checked since 7.0; it now carries the older JAR signature beside it, which
Android ignores and which that installer may have wanted. That is a
hypothesis rather than a finding: the message is the one Android's own
installer shows when the platform refuses a package, and the platform never
reads a JAR signature beside the newer one. This release installing on a
OnePlus would not settle it either, since the download and the build change
too; the same APK signed twice with one key, with and without the JAR
signature, and tapped on that phone, does. A release now also refuses to
publish an APK missing either signature. - Starting a session in the macOS app put blank "exec" icons in the Dock.
Every MCP server an agent started throughnpxappeared there as an application
of its own — a blank tile labelled "node" — because macOS counted the Node
runtime inside the app as part of Reemoat itself. The runtime now lives in a
small helper inside the app that macOS keeps out of the Dock, so MCP servers run
exactly as before and the Dock shows Reemoat alone. The app is no larger: it
still carries one copy of the runtime.
These builds are AGPL-3.0-only, and conveying a binary is a distribution.
The corresponding source for this release is the Source code archive on this
page and https://github.com/rends-east/reemoat/tree/v0.11.0.
v0.10.1
Added
- The app, for five platforms, on the release page. macOS for Apple silicon
and for Intel, Linux as a.deband an AppImage, Windows as an installer, and
Android as a signed APK. Each is built on every push by a check that builds
exactly the bundle the release publishes, and a platform cannot be added to a
release without one.
Fixed
- 0.10.0 has no release page, and this is the release it should have been.
0.10.0 was tagged and its control-plane image published, but its release failed
while building the Linux app, so none of the apps were published and there are
no 0.10.0 downloads. The code is the same: everything listed under 0.10.0 below
reaches the apps here for the first time. - A release that failed still published its image. The image tags people
pull — the version's own andlatest— were created as soon as the image was
built, whether or not the apps built, which is how 0.10.0 ended up with an image
and nothing else. They are now created only after every app has built, so a
release that fails leaves nothing behind and can simply be run again. - The Linux app could not be built by a release. The check that builds it on
every push installed the system libraries it links against; the release job that
builds the same app did not. Both now install them from one list.
These builds are AGPL-3.0-only, and conveying a binary is a distribution.
The corresponding source for this release is the Source code archive on this
page and https://github.com/rends-east/reemoat/tree/v0.10.1.
v0.9.0
Added
-
A message sent while the agent is working is taken rather than refused.
Correcting an agent mid-run no longer means pressing Stop and losing whatever
the turn had half-done. Where the agent supports it — claude and codex, over
the_session/steeringextension they advertise oninitialize— the message
goes straight into the turn already running. Where it does not, the daemon
holds the message and hands it over the moment the turn ends, and the
transcript says so under the message. The queue is the daemon's, so it survives
a closed tab, a sleeping phone and a dropped connection; it does not survive a
daemon restart, and a session stopped with messages still waiting records that
they never arrived. -
POST /sessions/:id/promptanswers202for a message sent mid-turn, carrying
steeredorqueuedbeside the usualseq, where it previously answered
409 turn_in_flight. A daemon that has not been updated still answers the
409, and the web client keeps its old behaviour against one. -
Work an agent leaves running after the turn that started it is visible, and
can be stopped. A backgrounded shell, a monitor or a workflow used to end
with the tool call that started it — the card readcompletedwhile the
command ran on for minutes, and nothing in the app could say what was still
going. The transcript's still-working row is a control now, and it opens a
Backgroundpanel: a bottom sheet over the conversation on a phone, docked
against the right edge on a wide screen, with a card per task carrying what it
is, how long it has been going, what it has spent, and Stop wherever the agent
says the task may be stopped. A tool call that detached saysRunning in the backgroundinstead of reading as finished.It rides
jetbrains.air, which is a vendor_metaextension rather than
core ACP — the draft surface foragent-client-protocol#1992— declared on
initializeand answered by claude alone. Measured 2026-09-11 against claude
2.1.268 under claude-agent-acp 0.73.0; the other three agents send nothing
here. kimi backgrounds shells, agents and cron jobs and maps its own
terminated event nowhere, codex'sunified_execPTY outlives the call it was
made in with no push at all, and opencode'sbashtool has no background flag.
So an empty panel says which emptiness it is:No tasks currently runningis
only sayable about an agent that would have told us, and the other three get a
sentence naming the silence instead.Three limits, stated because each of them is somebody's build. A session
claude reports live work in is not parked by the idle sweep and is not the
one released when a machine hits its ceiling — the work outlives the turn, so
the half-hour that is enough for an idle conversation is not enough for this
one. That protection is exactly as wide as the signal it rides: the other three
agents report nothing, and even under claude a backgrounded subagent is
announced as nothing at all, so for those the only thing standing between a
build and a released agent is still the floor on how young a session may be and
still be taken at the ceiling — two minutes. Nothing here survives a daemon
restart: a resumed session starts with an empty set, and the transcript
carries a row saying how many tasks were running when the agent was shut down,
because the tasks themselves went with the process. And a/clearcloses the
ACP session those tasks belonged to, which is what ends the shells it was
holding — a rung below that nothing here can see, so the transcript claims only
that the work was going and that this daemon can no longer say anything about
it. The rows go with the conversation, and a row of its own says how many were
going, since otherwise the only trace of a build somebody was waiting on
vanishes with the panel. -
POST /sessions/:id/async-tasks/:taskId/stop, andbackgroundTasksand
reportsBackgroundTaskson the session snapshot. Stopping answers200with
stopped: falsewhen the task ended on its own between the tap and the request
— that is an ordinary lost race, not an error —404for an id this session
never announced, which is a different sentence, and502where the agent could
not be asked at all, which is nobody knows rather than it was already over;
the card says so where it was pressed. Both snapshot fields are optional on the
wire, so a client reading a daemon that has not been updated draws exactly what
it drew before any of this existed.
Changed
- The composer's send slot follows what you have typed rather than what the
agent is doing. With the box empty it is Stop, as before; with anything in it
worth sending it is Send. Whitespace does not count, so a stray space or tab
leaves Stop where it was. - The transcript's "still working" row opens the background panel rather than
unfolding a list beneath itself. It used to be a disclosure drawing the
agent's outstanding delegations inline; two surfaces listing one set is how
they come to disagree, and the inline one could not grow to hold a task card
without pushing the composer down the screen every time an agent backgrounded
a shell. The row keeps the job it was always good at — saying, in the
conversation, that something is still going — and is pressable.
v0.8.0
Added
-
Terms of Use, an Acceptable Use Policy and a Privacy Policy, at
/terms,
/acceptable-useand/privacy. Readable with no account, because the sign-up
form links to them and because they are the URLs somebody is given when they ask
what the terms are. Signing up now needs a ticked box, which gates the form and
is refused byPOST /v1/registerwithout anacceptedTermsfield — but nothing
is stored, so this instance still cannot prove what anybody agreed to and does
not claim to. All of it is off unlessREEMOAT_CP_LEGAL_DOCUMENTSsays
otherwise: an instance that has not claimed the documents draws no pages, no
box, and refuses nobody. The
documents are adapted from the 37signals policies (CC BY 4.0, credited on the
page) andgithub/site-policy(CC0), and they name one operator — a fork
must replace theOPERATORblock inpackages/web/src/legal/operator.ts. English
only. -
An idle agent is now shut down and the conversation kept. A session nobody has
touched forREEMOAT_IDLE_PARK_MINUTES— 30 by default, on by default,0to
switch it off — is stopped with a newparkedexit reason: the process goes, the
conversation, the worktree and the branch stay, and the next message brings it
back. There is no Resume control for one, deliberately; the composer is the whole
affordance. Measured before it was built: a resident agent is ~397 MB and comes
back in ~1.3s at the median, so a machine holding three sessions nobody had opened
in 48 hours was holding 1 384 MB to save that. It draws as an ordinary idle session
and says nothing, Stop still works on it, and its model and mode chips stay live —
a tap is recorded and applied when the agent returns. -
GETandPATCH /settingson the daemon, and a control on the machine's
settings screen for the number above. A saved value overrides
REEMOAT_IDLE_PARK_MINUTES, which is therefore the default for a machine nobody
has set; it takes effect without a restart. The daemon's configuration is still env
only — this is the narrower class of setting whose owner is the person using the
machine rather than the one who deployed it. -
parkedjoins theSessionStatusandExitReasonunions, and it is neither
interrupted(the daemon owes it back by itself) norexited(somebody ended it).
A client older than this release has never heard of it; it keeps the composer and
reads the session as live, which is the safe direction, but it will label the exit
ended: parked. Deploy the control plane, which carries the web client, before the
daemons. -
Give up a share somebody made to you —
DELETE /v1/machines/:id/grants/me,
driven bycpctl leave <machineId>. Sharing writes a permanent row for any user
id with nothing asked of the person named, and the three verbs beside it all
resolve through ownership — so until now the only account that could undo a share
was the one that made it. Your own grant only, and never on a machine you own:
the machine list is a join overgrants, so an owner without one would own a
machine that appears in no list. Retiring it is the verb for that. -
Share a machine you own —
GET·PUT·DELETE /v1/machines/:id/grants,
driven bycpctl shares/share/unshare. The other person is named by
user id, which they read offcpctl meand tell you: there is no directory
an ordinary account may read, and a name lookup here would be a way for anyone
signed in to test whether an account exists. Your own grant is refused on both
writes — narrowing it would takemachine:adminoff your own hardware, and
removing it would hide the machine from its owner. Retiring the machine is the
verb for giving up your own access. -
enrolledByonGET /v1/machines, and drawn on the machine row: whose
enrollment code a machine enrolled with, when that was not yours. It is the
disclosure for a composition no single refusal closes — revoke somebody's
machine, register a new one under the name that frees, enroll it on your own
hardware, and their list draws the name they lost, owned and online. Every step
has to stay, so the composition is made visible instead. A name is something to
recognise rather than an alarm: the installer's wizard enrolls on an admin's
code too.Machines that enrolled before this column existed — which on the day of the
upgrade is all of them — say somebody this control plane did not record rather
than nothing, because nothing is what a machine you enrolled yourself draws and
folding them together would have left the disclosure silent for exactly the
population it is for. There is no backfill: the table it would read from is swept
seven days after a code is used.Two limits, stated because a disclosure nobody has bounded gets trusted past what
it says. It names who minted the code, never who redeemed it —POST /v1/enrollis public and a daemon presents no account, so a code of your own
that leaks and is redeemed elsewhere reports you. And it moves when the machine
list is read, on a wake or a reload, not on the four-second poll. -
The session rail is ordered by you. Grab a chat with the mouse and drag it up or
down inside its folder; on a touch screen hold it briefly first, so the gesture
and scrolling the list stay apart. Drop it in Pinned to pin it, and drag it back
out to unpin.Alt+↑/↓on a focused row does the same from a keyboard. The
order is stored per machine on the daemon, so one set from a laptop is the one a
phone opens. -
Up one folder in the New session directory picker, beside the path — a 44px
square with a ground of its own, sized for a thumb. Always drawn and greyed out
at the top of the tree rather than appearing when it becomes usable; the path
itself is still tappable segment by segment. -
A way back from Import code. Its ✕, Escape and a tap outside now return to
New session with the machine, agent and folder still chosen, and there is a
chevron that says where it goes. Closing it mid-upload also stops the upload,
which used to hold the machine's import lock. -
A second control on the error screen: Go to sessions, beside Reload. A
screen that throws every time it renders made Reload a loop. -
An instance can point somebody who has no machine at somewhere to get one.
REEMOAT_CP_MACHINES_OFFER_URLin the control plane's environment takes an
https://address, and the three screens that already print the one-line
installer draw a second, quieter link beside it. Environment-only on purpose:
it names one particular shop, and a runtime setting would draw it on the
Server settings screen of every instance. Empty by default, and an instance that never sets it
looks exactly as it did before. The signed-in person's email address travels in
the link as?email=, so a checkout on the far side can prefill its own form;
it goes only when they tap it, and every response here already carries
referrer-policy: no-referrer, so nothing else about the instance goes with it.
The address is configured rather than compiled in because this is AGPL software
and forks run their own control planes.
The offer is drawn only where a machine may still be added: at or over the
machine limit a bought host would be refused at the dial, so offering one there
would sell something this control plane will not connect.
Changed
-
A machine at its session ceiling now releases an idle agent instead of refusing.
POST /sessionsand any wake take the least recently used idle slot rather than
answering429, and a turn in flight, an unanswered permission and an unanswered
question are never taken at any ceiling. A create is still refused when every live
session is genuinely busy, and the sentence says so. The ceiling therefore counts
agents resident rather than conversations held. -
An admin may no longer mint an enrollment code over a live one somebody else
made.POST /v1/admin/machines/:id/enrollmentsanswers409 code_outstanding.
Minting supersedes the machine's current code, so on a machine that is owned but
has not enrolled yet — which is what an install in progress looks like — an admin
minting here killed whatever the owner was holding: their install failed against
the deliberately undifferentiated409 code_unusable, nothing on their screen
said why, and it could be repeated for as long as somebody cared to, because
enrolled_atnever left NULL and the guard beside it never started applying.
The two uses that stay are the two it was for:install.sh's wizard mints on a
row that has no code at all, and an admin re-minting their own supersedes only
what they are replacing. -
Deleting an account no longer strands a machine it was the last person on.
DELETE /v1/admin/users/:idalready revoked the machines the account owned; a
machine it merely held a grant on was left ownerless, enrolled, dialling the
relay and in nobody's list. That is the failure user-owned machines exists to
remove, and it was also a way to manufacture the state the two admin guards
protect — delete the last grantee, and an ownerless enrolled row becomes
adoptable with every scope. Scoped to machines the deleted account was actually
on: a legacy row that was already grantless is left exactly as it was. -
An admin may no longer take a machine off the person who has it.
PUT /v1/admin/machines/:id/owneranswers403 machine_ownedfor a machine with a
live owner other than the target, and403 machine_grantedwhen adopting an
ownerless machine somebody already holds a grant on unless they are the one
being handed it. What it still does is what it was written for: adopting a row
registered before ownership existed, and re-labelling a machine for the owner it
already has. Adopting now also burns that machine's...
v0.7.0
Changed
- The fleet's
claudefollows thelatestrelease channel by default, and the
channel is a setting:REEMOAT_AGENT_CHANNEL=stable|latestin the daemon's env
file,--agent-channelon the one-line installer,--channelon
deploy/agents.sh. Both daemon hosts were onstableat 2.1.236, a build that
had never heard of the newest model, whilelatestwas 2.1.261. The refresh
runsclaude install <channel>rather thanclaude update—updatefollows
whichever channel the last install wrote into claude's own settings, so a host
installed onstablewould have stayed there whatever the env file said. A
change of channel moves the machine on the next run, down as well as up, with
the previous build kept on disk and no session interrupted. PUT /v1/me/emailasks an API-key caller with a password forcurrentPassword
—400 bad_requestwithout it,401 invalid_passwordwith a wrong one, and
nothing is written or mailed until it verifies. A session still changes the
address alone, and an account with no password row is still let through on
its key. The address is the reset channel; a key can leak from a disk with no
person anywhere in the chain and no admin reset behind it, where a session is
a person signed in, listed under Devices and one tap to end.cpctl keyno longer prompts for a password the route never read.cpctl emailstill does when the shell holds an API key, and asks nothing when
REEMOAT_CP_KEYcame fromcpctl login, since the route ignores a password
from a session.cpctl keysprints when each key was last used.- Every two-step confirmation on a settings row is one control,
TwoStep. The
first tap still replaces the row's buttons with the question, the act and
Cancel — Cancel last, on the same pixels, the question standing until the
server has answered — held in one place and pinned once rather than
re-derived on each of fourteen sites. The two centred confirmations keep
their shape; the question reads in the text colour everywhere, with its
consequence muted under it. Signing an agent out on a machine that has left
the list is refused, greyed, rather than answered with a toast. - Settings say less, again: the New session strip's lede, the two limit
consequences, the not-enrolled line, the agent card's unknown-sign-in line,
codex's two caveats and the plugin consent'shttpcaveat are cut to the caps
the plan set, with the same facts in fewer words.
Removed
- An admin's view of anybody else's API keys.
GETandDELETE /v1/admin/users/:id/keys[/:keyId]are gone, the fleet list no longer counts a
person's live keys, and the "API keys" item in a user row's menu — with the
panel it opened — is gone with them. A key is listed and retired by the
person holding it, on their own API keys screen or withcpctl keys; an
admin's reach over an account is disable and delete.
Fixed
- A settings pop-up drew two scrollbars it had no use for on a desktop: a
horizontal one along its foot and a vertical one down its right edge, on a
screen that fit. A sheet's body was a padded scroller whose every screen
cancelled the padding with negative margins, and a scroll container counts
its own end padding past the content, so each axis had one padding of range
nothing could show. The body no longer scrolls or pads — every pop-up scrolls
in a box of its own — and inside the settings and plugins pop-ups the section
rail and the pane scroll with no bar drawn at all. - Every row of the API keys table is the same height. A row with a Revoke
button was the button's height plus padding and a revoked row was its text
plus the same padding, a third shorter. - The startup prune deletes only inactive sessions, never below fifty, and says
what it removed. It readcreated_at, so a conversation older than seven days
from the day it was opened was deleted at the next restart however much it
was in use — on 2026-09-04 one deploy's restart deleted five of the six
conversations it had just stopped, with their transcripts (~50 MB), and the
journal held nothing butrestored 1 session(s). A session is now swept only
when it was ended by a person or by the agent, never started, or given up on
because the agent no longer holds the conversation (only a manual Resume
tries again) — never a live one, and never one the daemon ended on its own
restart or shutdown and is still coming back to, at any age and under any
cap — and only once untouched for seven days by its last write; a prune
never leaves fewer than fifty rows (REEMOAT_MIN_SESSIONS) — the ones it
never sweeps first, then pins, then the most recently touched, whatever
their age; the two-hundred cap is on the rows nobody is coming back to and
takes the least recently touched of them, pins last; and every id that went
is printed at startup on its ownstore:line. - A valid API-key request no longer answers a plain-text 500 when the
last_used_atbookkeeping write meets a busy database: the write is guarded
the way the session one already was, so a request that could not record its
own use still succeeds. - Reset on an Email field survives the next Save. Save sends every SMTP field
from the draft, and a Reset re-synced the draft only while the form had no
other edits — so edit Host, Reset From, Save wrote the old From straight back
under a "Saved." toast. - Public URL is filled in with the page's own origin on a fresh server, since
mail cannot be sent without it and the field drew the origin only as a
placeholder. Save is live at once; the provenance line says "not set" until
it lands. A value already stored or set in the environment is left alone. - New key waits for the key list to load rather than opening the leaf during
the skeleton only to be told the ceiling. A list that failed to load still
lets you mint. - Every Revoke on the keys screen and under a user's keys names its key to a
screen reader. - Three controls no longer stay live during the write they belong to: a
field's Reset while a Save is in flight, the strip's Remove while its delete
is out, and the device-code box, which empties after the code is written so a
failed send leaves it in the box beside the toast rather than gone. - The machines list no longer jumps on load: the loading row is the height of
the machine row it stands in for. And a retired machine leaves the list at
once rather than a round trip later. - Revoking the key this browser holds still signs the tab out when browser
storage is blocked. The one-shot notice for the sign-in screen was written
unguarded, and a browser with storage disabled threw there before the
credential was cleared — so the tab kept a dead key and the next request said
"Your session expired" about an act the person had just chosen.
v0.6.0
Added
-
users.password_changed_atandapi_keys.last_used_aton the control plane,
both additive:GET /v1/meanswerspasswordChangedAtand both keys routes
answerlastUsedAt, touched on an accepted bearer lookup at most once a minute
and never on a revoked key. An older client ignores the fields and an older
control plane answers without them. -
deploy/ci-freshness.sh, run weekly by.github/workflows/freshness.yml,
compares each ACP adapter pin inpackage.jsonwith what the npm registry
serves and writes the answer into the job summary. Being behind is a report
and never a failure (FRESHNESS_MAX_BEHINDis the margin that makes it one);
a pinned version the registry no longer lists fails the job, since it would
failpnpm install --frozen-lockfileon the next machine the one-line
installer sets up; and a registry that could not be asked exits with its own
code rather than a verdict about the tree.pnpm deploycheckdrives every
outcome offline through theNPM_VIEWseam. -
renovate.json: pull requests proposing dependency bumps, the two ACP
adapters one each and the rest of the workspace grouped weekly, with exact
pins and no automerge — a person merges, and each machine still takes the
change throughdeploy/deploy.sh. -
The daemon keeps the coding-agent CLIs current by itself.
deploy/agents.sh
installs what is missing — three with each vendor's own installer into the
vendors' own directories (~/.local/bin,~/.local/share/claude,~/.codex,
~/.opencode), kimi from the npm registry into~/.reemoat/toolchain— and
refreshes what is there; the one-line installer runs it once, and the daemon
runs it five minutes after start and then daily. On by default —
REEMOAT_AGENT_UPDATES=offswitches it off, anddeploy/agents.sh --checksays
what a run would do without doing it.--uninstallremoves the toolchain, and
the npm-installed CLIs with it, and leaves the vendors' directories in place
because they hold sign-ins of your own. -
GET /agents/capabilitiesrows carrycli: which build of the harness's own
CLI published the model list, and whether it was an operator's override
(override) or the copy found on PATH (path). The model picker draws it under
the provider heading. Absent from older daemons;nullwhere nothing was
spawned. -
The daemon announces which build of each agent CLI it would launch on the
tunnel handshake (x-reemoat-agent-clis,claude=2.1.259;codex=0.153.1;kimi=-),
beside its own version and under the same rule: recorded against the machine,
never acted on.GET /v1/admin/fleetanswers it asagentsper machine and
cpctl admin fleetprints it, offline machines included — so "which machines
are running a July claude" is answered without touching one. As fresh as the
machine's last dial; a daemon older than the header is listed withnull, and
a value the relay cannot read is refused tonullrather than costing the
tunnel. -
CLAUDE_CODE_EXECUTABLEandCODEX_PATHare documented in.env.example; a
harness named there runs as named, and the daily refresh leaves it alone. -
REEMOAT_AGENT_SOURCE=npm, and the one-line installer's--agent-source npm:
all four CLIs from the npm registry rather than the vendors' own hosts, for a
machine that cannot reach them. Your mirror is named the way npm is pointed
anywhere (~/.npmrc,npm_config_registry); everything lands under
~/.reemoat/toolchain. A choice rather than a fallback — a vendor outage never
switches a machine to a differently built binary on its own. It decides only
how a missing CLI is installed: one already on the machine keeps being refreshed
the way it was installed, and undernpma vendor-installed copy is reported on
every run until it is removed. -
A model id can be typed under a routed provider in the agent builder — a
field at the foot of Moonshot's, Z.ai's, MiniMax's and OpenRouter's rows — for
an id the written-down list does not hold. It becomes a row like any other, with
the same key and pairing rules, and picking it names the agent after it. The
written-down lists are a starting set and were refreshed against each vendor's
documentation: Moonshot's three had all been retired (kimi-k3,
kimi-k2.7-code,kimi-k2.6now), Z.ai gainsglm-5.3andglm-4.7, MiniMax
gainsMiniMax-M3andMiniMax-M2.7. The same mechanism lets an assembled
agent whose model has since left every list be renamed and saved rather than
drawing an empty Model field.
Changed
- Settings is six sections — Account · API keys · Machines, then under an
"Admin" heading Server · Email · Users. API keys are a table on a screen of
their own, with when each was made and last used and which one this browser
holds; New key is one tap, and the key is shown once on/settings/keys/new.
Email (SMTP, the test send, delivery trouble) is split out of Server;
registration is a badge and a verb button, reminting the provisioning key is
two-step. Password and email changes are their own screens rather than forms
opening inside a row — nothing on a settings screen expands in place. Every
confirmation names its subject with Cancel last; help prose is cut to the caps
the plan set, and the machine row marks a machine that is not yours with a
sharedbadge. Revoking the key this browser holds signs the tab out on
purpose, and the sign-in screen says so. POST /v1/me/keysandPUT /v1/me/emailtake the session alone: neither asks
for the current password any more, and acurrentPasswordin the body is
ignored rather than verified.POST /v1/me/passwordstill asks. The cost of
the email half is written at the route.@agentclientprotocol/claude-agent-acp0.63.0 → 0.73.0 and
@agentclientprotocol/codex-acp1.1.9 → 1.8.0. Re-measured againstclaude
2.1.259 andcodex0.153.1: the model chip reads the model claude'sDefault (recommended)stands for rather than the placeholder (the bullet below is the
rule that makes it so); codex answersopenaias its provider id where 1.1.9
answeredcustom-gateway, and nothing had that written down; codex publishes a
collaboration-mode control and anultraeffort level, and claude anAgent
persona control wherever.claude/agents/has a file — each drawn as a plain
labelled control.node_modulesis 230 MB on darwin-arm64 after the bump.- The model picker also collapses claude's
Default (recommended)row onto the
model it stands for when the placeholder's description is that row's name,
which is what claude-agent-acp 0.73.0 publishes; 0.63.0 copies the row's blurb
instead, and that case still collapses. Without the second rule the placeholder
came back after the adapter bump and the chip read "Default (recommended)". - A session whose harness has no CLI on the machine yet is left waiting rather
than given up on: the boot pass spends no attempt on it, runs the agent
installer at once instead of in five minutes, and tries again when it
completes. The daemon's log shows every completed agent update, with the
script's own notes under it. Only a missing CLI is treated that way — a missing
adapter package or a plugin harness that is gone spends its attempts and settles
as before, since the installer cannot put either back — and only the first run
is pulled forward; after it the daily run is the retry. The session row says
" is not installed on — waiting for it to be installed" for
the wait, rather than the plain restart line. - An empty fleet is set up from the content pane on a wide screen: the one-line
installer and one line about it draw beside the rail rather than inside it, at
a width the command can be read at. The command box wraps instead of scrolling
sideways, on every screen that draws one. - A machine is added by running the one-line installer on it, and only that way.
The by-name form on Settings → Machines, which handed back a setup code to
carry to the host, is gone, and so is the "Add a machine" button that led to
it;cpctl enrollstill mints a code for an operator who needs one. Since
nothing in the tab adds a machine any more, an empty fleet is re-listed on
every poll, so the machine the script enrols appears by itself. - Settings say less. Sentences that restated the control beside them, explained
a mechanism nobody acts on, or printed a URL already on screen were cut
across Account, Server, Users, Machines, Systems, Agents and Plugins; every
consequence a person cannot see stays. - The one-line installer unsets
REEMOAT_API_KEYas soon as it has read it, so
the account key no longer reachespnpm install's lifecycle scripts or the
vendor installersdeploy/agents.shdownloads and runs. - The coding-agent CLIs are no longer installed by
pnpm install. The two ACP
adapters stay pinned; the CLI platform packages they used to bring with them are
excluded throughpnpm-workspace.yamloverrides, andnode_moduleswent from
907 MB to 217 MB on darwin-arm64 (230 MB after the adapter bump above).
deploy/agents.shinstalls the only copies
there are, and the daemon runs an operator's override, else the first copy on
PATH, then in the directories that script installs into — re-decided every ten
minutes, so a refresh under a running daemon is picked up. A harness with no CLI
on the machine is refused with a sentence naming the script rather than started. deploy/deploy.shinstalls or refreshes the agent CLIs before it restarts the
daemon, so a machine upgraded from a release that vendored them comes back with
its harnesses rather than waiting on the daemon's first daily run.--sourceandREEMOAT_AGENT_SOURCEdecide only how a missing CLI is
installed; one already present is refreshed the way it was installed — a
vendor-installed copy by the vendor's own updater, an npm-installed one...
v0.5.0
Added
-
One command puts a machine in the app.
curl -fsSL <release asset> | sh
installs the daemon, asks which control plane to join and who you are on it —
sign in, sign up, or paste a key — creates the machine and starts the service.
Nosudo, nothing written to a shell profile, and nothing outside~/.reemoat
and the checkout, which is what makes--uninstallcomplete rather than
approximate. It hands the last third todeploy/install.sh daemon --non-interactiverather than reimplementing unit rendering, PATH computation
and the health probe.Where the software comes from and which fleet it joins are two questions,
and the command keeps them apart. The README downloads from a release asset
on this repository; a control plane serves the same file atGET /install.sh
with its own origin substituted in, which is what Settings → Machines prints
and why there is nothing to type there;--urlorREEMOAT_CONTROL_PLANE
says it outright. Fetched from anywhere neutral it asks, with no default —
letting a download URL also decide which fleet a machine joins is how somebody
who wanted their own control plane arrives in one they do not run.Three things about it are load-bearing rather than tidy. Everything runs from
onemain "$@"on the last line, becausecurl … | shexecutes bytes as they
arrive and a truncated download otherwise runs a prefix of the file with
set -esilent. Every question is asked on/dev/tty, because stdin is the
download. And the origin substituted into the served copy is shell-quoted: a
Hostofa`id`breachesURL.originintact, andimagechecksends one
through a real container to prove the quoting is on the path a request takes. -
A host can pull the control-plane image instead of building it.
REEMOAT_CP_IMAGEdecides — a registry-qualified ref pulls, a bare one builds
— anddeploy.shprints which on every run.REEMOAT_CP_SOURCEoverrides the
derivation, and an unrecognised value is refused rather than defaulted.In pull mode
CP_IMAGE_INPUTSdoes not apply: a git diff is a guess at what a
build would produce, and a registry ref names exact bytes. Everything
downstream is untouched —cp_image_fingerprintinspects the local image
either way, soCP_IMAGE_MOVEDand theRELAY_INPUTSrule that decides
whether the fleet's tunnels drop behave exactly as they do after a build.
Fixed
-
The documented way to run the published image had never worked.
deploy/README.mdsaid to putREEMOAT_CP_IMAGE=ghcr.io/…in the control
plane's env file. It could not win: compose gives the shell environment
precedence over--env-filefor${…}interpolation, andcompose.sh
exported its own default before compose ever ran — measured,compose.sh configwith a registry ref in that file still printed
image: reemoat/control-plane:current.The half with no symptom is worse.
deploy.shcallscp_image_fingerprint
from a process where that variable may be unset while compose's child had it
from the file, so the fingerprint would inspect a different name, answer
empty on both sides, conclude the image had not moved, and recreate nothing —
a deploy that printed success while the old bytes kept serving. There is one
resolver now (cp_image_ref), every script reads it, anddeploycheck
asserts no script holds a second copy of the default. -
GET /install.shanswers with the scheme a browser actually used.
publicUrltakes it fromsocket.encrypted, and the service runs plain HTTP
behind a proxy that terminates TLS — so it answeredhttp://, and the plain
form of a deployed origin is a301the installer deliberately does not
follow.x-forwarded-protois read, gated ontrustedProxyHopsexactly as
callerAddressOfis. ⚠ The same defect oncontrolPlaneUrl(four
code-minting routes) is known and not fixed here: changingpublicUrl
itself changes what every enrollment paste has said since the first release.
v0.4.0
Fixed
-
A pasted key no longer expires on its own, and a machine left alone over a
holiday comes back signed in. There was a sweep inprune()that deleted a
saved agent credential once it was older than the session horizon and no
sessions were left. Both halves had to be true, which read like a narrow rule and
was not:updated_atmoves only when a key is pasted — nothing touches it on
read — so the age half was permanently true of every key in real use, and what
was actually left binding was "no sessions left", which unpinned sessions reach
after seven days. Eight idle days and a restart, and the paste was gone, with
nothing on any screen connecting the two. It is removed: a key in
agent_credentialsorsystem_credentialsnow goes when you clear it, when a
paste replaces it, or when you uninstall the plugin that added the harness or
provider it belongs to — and at no other time.⚠ A daemon that ran an earlier build may already have deleted keys this way,
and there is nothing to restore — the sweep was a plainDELETE. If a machine
that has been quiet for over a week now reports an agent as signed out, that is
this bug rather than anything at the vendor: paste the key again under Settings →
Machines → this machine, and it will stay. Deleting a local copy never revoked
anything at the vendor, which is the largest reason the sweep was not worth its
cost.docs/DECISIONS.mdQ7.124 carries the whole argument. -
A harness that will not start stops being offered, and stops costing a
worktree. Reported with a screenshot: New session drew a tile for an agent a
plugin had added, Start answered "rejected session/new: authentication
required", and the tile was still there for the next press — and the one after
that. The daemon remembers a refused start now, so the tile goes, the model
picker greys the row, and a second press is refused before a worktree, a branch
and a session row are made for it. It is remembered for ten minutes rather than
written down: an agent's refusal is an observation, and this project has once
already turned one into a standing verdict and stranded conversations under it.
A successful start forgets it, so does pasting a key, so does finishing a sign-in
in the app, so does switching the plugin off and on — and where none of those
happened there is Check again, on the machine's agent list and on the agent's
own card, for after you have signed in on the machine itself.
pnpm client agents recheck <agent>is the same thing from a terminal. -
A settings card called an agent a plugin added by its identifier. Every
sentence on that card named the harness with this product's own table, which
answers the bare id for anything it does not ship — so the card read "byo:gemini
needs no sign-in". It uses the manifest's name now, bounded, exactly as the
tiles and the refusal sentences already did. -
A resumed session no longer reports a demotion that did not happen. An agent
that comes back on the model it was started with, and has since stopped listing
that model, drew an error row contradicting itself in one sentence — "has no
model called X … resumed anyway, running X" — because the pin asked whether the
model was offered rather than whether the session was on it. Worse, the
same refusal madePOST /sessionsanswer a permanent502for a preset whose
model the agent was running. Both gone: the pin answers "already there" before it
reads the list.
Changed
- Settings → Machines → Systems is now Sign-ins, and it holds both halves.
Signing in on a machine was never only about inference: a harness can read a key
of its own, and until now the only place that key could be typed was a
provider's card — reached when that provider named the harness. Every harness
this product ships is named that way, so all four had one; a harness a plugin
added had one only if that plugin also contributed a provider naming it, and
nothing required that. A key box could be declared in a manifest and drawn on no
screen at all. Such a harness has a row of its own now, after the providers,
saying whether a key is saved. Nothing changes on a machine with no plugins —
adding every harness would have put Claude Code beside Anthropic, which is two
rows for one account.
Added
-
A plugin can add a harness, or a provider. Two declarative blocks in
plugin.json—contributes.harnessesandcontributes.systems— put an ACP
program and an inference endpoint on a machine, and both then behave as though
this product had shipped them: the harness is in the agent builder's harness row
and in Settings → Machines with a paste box, and the provider is a heading in the
model picker and a key box beside the built-in ones. The two are assembled into a
named agent exactly as Claude Code on OpenRouter is, and that is what gets a
tile on New session — a plugin adds a harness, never an agent, because whether a
harness is a whole answer on its own is a claim about the model that nothing on
the machine can check. No plugin code runs for either and nothing is fetched at
runtime; the daemon reads the manifest and answers its own routes with it.A machine that has just been told about a harness asks it whether it starts,
rather than leaving that to whoever presses Start first: installing, updating or
switching a plugin on runs the same capability read the agent builder does, so
the answer is on screen before anybody taps anything.Both need plugin API 5, and a daemon that speaks less refuses the block rather
than ignoring it — a plugin whose whole reason to exist is a harness has no useful
degraded form, so its owner is told to update the machine instead of installing
something inert. Two new scopes say what is at stake, and the consent screen also
draws the command line and the whole base URL, because a line in a list is
where somebody learns a capability exists and not where they can judge one. What
is drawn is exactly what the daemon compares, so a commit asking for more than was
shown is refused before the plugin runs — including from a browser too old to have
drawn the rows at all.A provider's endpoint may be
httpsanywhere, orhttpto this machine or this
network, which is what makes Ollama, vLLM and LM Studio reachable; where it is
httpthe consent screen says the key travels in the clear.
rends-east/reemoat-plugin-byois a working example — Gemini CLI as the agent,
DeepSeek as the provider, both measured — and it holds no scope that gates a
method, so its consent card shows the two things it adds and nothing else. A
contributed harness
has no sign-in wizard and will not get one — everything a wizard needs is a
measurement about somebody else's CLI — so it is opencode's shape: a paste box and
nothing else. Uninstalling a plugin now takes its saved keys with it, out of both
credential tables; switching one off keeps every session, preset and position it
had, and putting it back brings them all back. -
The agent row on New session is yours to arrange. The gear at the end of it
opens a per-machine Agents screen — reachable from the machine's own settings
too — where every agent that row can offer is a full-width row you can drag,
hide, edit and remove, with the+that used to sit in the strip now at the foot
of the list it adds to.The order and the hidden set live on the daemon, per machine, so they are the
same from a phone and a laptop and survive the reload a phone performs on its
own. What is stored is a partial record — a position for what somebody actually
moved — merged over whatever the machine currently reports: an agent it has never
heard of appends at the end and is visible, and an agent that is signed out or
gone keeps its place for when it comes back rather than losing it.Every row is draggable and movable from the keyboard, with
↑/↓/Home/End
on the handle — a 44px target that keeps the sheet from scrolling under a thumb.
Everything a row can do is behind one menu, on every row, and every row can be
edited and removed: a built-in agent and one you assembled are the same thing from
the picker's side — the built-in one is just the one that is there by default.
Editing it opens the builder already pointed at that harness. Removing signs nothing out. A built-in row stays where it is,
dimmed, offering to add it back; an assembled one is deleted and rebuilt from Add
an agent, and sessions started on it keep resuming on the bare harness. There is
no confirmation, because neither is a thing you cannot redo.Opened from the gear, the screen's ◀ goes back to the New session sheet you left —
folder walked and agent chosen — rather than further into settings.A machine whose agents are all hidden says so, and points at the gear.
The first agent in that order is the one a new session opens on, and its row
saysdefault. Drag another to the top and it becomes the default. "First"
skips a row that cannot be started — a hidden one, a harness that is signed out,
an agent assembled on a harness that has since been uninstalled — because those
have rows here on purpose and none of them is what a new session can open on. It
also fixes the picker: the last of those three used to be selected on arrival and
then refused, leaving New session with no agent chosen and Start dead until
you tapped one. -
A built-in agent's tile says which system it runs on. Claude Code ·
Anthropic, Codex · OpenAI, Kimi Code · Moonshot — on the New session tiles and on
the rows of the Agents screen. It used to saysigned in, which is true of every
agent you can actually start and therefore says nothing; a status only appears
now when there is one worth reporting, and it displa...