Skip to content

Releases: rends-east/reemoat

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 10:16
6cd6f77

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

Choose a tag to compare

@github-actions github-actions released this 01 Oct 17:27
568beec

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.json for 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 @mira under 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 --nickname and pnpm client nickname set 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=off removes 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=off still 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/dev rather 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.sh pins the new DEPLOY_KNOWN_HOSTS secret with StrictHostKeyChecking=yes and refuses to deploy without it. Add the secret before the next dispatch: the host's keys as ssh-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_message to 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...
Read more

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 23:37
a437f9b

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_URL drew 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 from GET /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 ~/.reemoat named
    the first. Each account now gets its own daemon, database, sessions and working
    copies — ~/.reemoat stays with the server its daemon.env names, 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_SOURCE and
    REEMOAT_AGENT_CHANNEL in ~/.reemoat/daemon.env do 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 daemon from a checkout); it adopts it.
  • A daemon's clean stop no longer deletes another daemon's announcement. Every
    daemon removed daemon.json on its way out, whoever had written it; now only the
    one that wrote it does.
  • install.sh --uninstall --purge names the desktop app's daemons for other
    servers and accounts before deleting them, and --uninstall lists them among the
    data it keeps.
  • The model chip read "Newer version availa…" after Claude Code moved its
    opus alias.
    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, or deploy/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
    over adb on 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 through npx appeared 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

Choose a tag to compare

@github-actions github-actions released this 22 Sep 18:47
58c5543

Added

  • The app, for five platforms, on the release page. macOS for Apple silicon
    and for Intel, Linux as a .deb and 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 and latest — 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

Choose a tag to compare

@github-actions github-actions released this 14 Sep 12:41

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/steering extension they advertise on initialize — 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/prompt answers 202 for a message sent mid-turn, carrying
    steered or queued beside the usual seq, 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 read completed while 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
    Background panel: 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 says Running in the background instead of reading as finished.

    It rides jetbrains.air, which is a vendor _meta extension rather than
    core ACP
    — the draft surface for agent-client-protocol#1992 — declared on
    initialize and 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's unified_exec PTY outlives the call it was
    made in with no push at all, and opencode's bash tool has no background flag.
    So an empty panel says which emptiness it is: No tasks currently running is
    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 /clear closes 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, and backgroundTasks and
    reportsBackgroundTasks on the session snapshot. Stopping answers 200 with
    stopped: false when the task ended on its own between the tap and the request
    — that is an ordinary lost race, not an error — 404 for an id this session
    never announced, which is a different sentence, and 502 where 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

Choose a tag to compare

@github-actions github-actions released this 10 Sep 21:50
395b042

Added

  • Terms of Use, an Acceptable Use Policy and a Privacy Policy, at /terms,
    /acceptable-use and /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 by POST /v1/register without an acceptedTerms field — but nothing
    is stored, so this instance still cannot prove what anybody agreed to and does
    not claim to. All of it is off unless REEMOAT_CP_LEGAL_DOCUMENTS says
    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) and github/site-policy (CC0), and they name one operator — a fork
    must replace the OPERATOR block in packages/web/src/legal/operator.ts. English
    only.

  • An idle agent is now shut down and the conversation kept. A session nobody has
    touched for REEMOAT_IDLE_PARK_MINUTES — 30 by default, on by default, 0 to
    switch it off — is stopped with a new parked exit 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.

  • GET and PATCH /settings on 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.

  • parked joins the SessionStatus and ExitReason unions, and it is neither
    interrupted (the daemon owes it back by itself) nor exited (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 by cpctl 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 over grants, 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 by cpctl shares / share / unshare. The other person is named by
    user id, which they read off cpctl me and 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 take machine:admin off 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.

  • enrolledBy on GET /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/enroll is 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_URL in 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 /sessions and any wake take the least recently used idle slot rather than
    answering 429, 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/enrollments answers 409 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 undifferentiated 409 code_unusable, nothing on their screen
    said why, and it could be repeated for as long as somebody cared to, because
    enrolled_at never 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/:id already 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/owner answers 403 machine_owned for a machine with a
    live owner other than the target, and 403 machine_granted when 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...

Read more

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 22:40
a19aa64

Changed

  • The fleet's claude follows the latest release channel by default, and the
    channel is a setting: REEMOAT_AGENT_CHANNEL=stable|latest in the daemon's env
    file, --agent-channel on the one-line installer, --channel on
    deploy/agents.sh. Both daemon hosts were on stable at 2.1.236, a build that
    had never heard of the newest model, while latest was 2.1.261. The refresh
    runs claude install <channel> rather than claude update — update follows
    whichever channel the last install wrote into claude's own settings, so a host
    installed on stable would 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/email asks an API-key caller with a password for currentPassword
    — 400 bad_request without it, 401 invalid_password with 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 key no longer prompts for a password the route never read. cpctl email still does when the shell holds an API key, and asks nothing when
    REEMOAT_CP_KEY came from cpctl login, since the route ignores a password
    from a session. cpctl keys prints 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's http caveat 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. GET and DELETE /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 with cpctl 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 read created_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 but restored 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 own store: line.
  • A valid API-key request no longer answers a plain-text 500 when the
    last_used_at bookkeeping 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

Choose a tag to compare

@github-actions github-actions released this 04 Sep 14:12
133b67a

Added

  • users.password_changed_at and api_keys.last_used_at on the control plane,
    both additive: GET /v1/me answers passwordChangedAt and both keys routes
    answer lastUsedAt, 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 in package.json with what the npm registry
    serves and writes the answer into the job summary. Being behind is a report
    and never a failure (FRESHNESS_MAX_BEHIND is the margin that makes it one);
    a pinned version the registry no longer lists fails the job, since it would
    fail pnpm install --frozen-lockfile on 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 deploycheck drives every
    outcome offline through the NPM_VIEW seam.

  • 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 through deploy/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=off switches it off, and deploy/agents.sh --check says
    what a run would do without doing it. --uninstall removes 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/capabilities rows carry cli: 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; null where 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/fleet answers it as agents per machine and
    cpctl admin fleet prints 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 with null, and
    a value the relay cannot read is refused to null rather than costing the
    tunnel.

  • CLAUDE_CODE_EXECUTABLE and CODEX_PATH are 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 under npm a 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.6 now), Z.ai gains glm-5.3 and glm-4.7, MiniMax
    gains MiniMax-M3 and MiniMax-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
    shared badge. Revoking the key this browser holds signs the tab out on
    purpose, and the sign-in screen says so.
  • POST /v1/me/keys and PUT /v1/me/email take the session alone: neither asks
    for the current password any more, and a currentPassword in the body is
    ignored rather than verified. POST /v1/me/password still asks. The cost of
    the email half is written at the route.
  • @agentclientprotocol/claude-agent-acp 0.63.0 → 0.73.0 and
    @agentclientprotocol/codex-acp 1.1.9 → 1.8.0. Re-measured against claude
    2.1.259 and codex 0.153.1: the model chip reads the model claude's Default (recommended) stands for rather than the placeholder (the bullet below is the
    rule that makes it so); codex answers openai as its provider id where 1.1.9
    answered custom-gateway, and nothing had that written down; codex publishes a
    collaboration-mode control and an ultra effort level, and claude an Agent
    persona control wherever .claude/agents/ has a file — each drawn as a plain
    labelled control. node_modules is 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 enroll still 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_KEY as soon as it has read it, so
    the account key no longer reaches pnpm install's lifecycle scripts or the
    vendor installers deploy/agents.sh downloads 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 through pnpm-workspace.yaml overrides, and node_modules went from
    907 MB to 217 MB on darwin-arm64 (230 MB after the adapter bump above).
    deploy/agents.sh installs 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.sh installs 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.
  • --source and REEMOAT_AGENT_SOURCE decide 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...
Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 16:13

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.
    No sudo, nothing written to a shell profile, and nothing outside ~/.reemoat
    and the checkout, which is what makes --uninstall complete rather than
    approximate. It hands the last third to deploy/install.sh daemon --non-interactive rather 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 at GET /install.sh
    with its own origin substituted in, which is what Settings → Machines prints
    and why there is nothing to type there; --url or REEMOAT_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
    one main "$@" on the last line, because curl … | sh executes bytes as they
    arrive and a truncated download otherwise runs a prefix of the file with
    set -e silent. Every question is asked on /dev/tty, because stdin is the
    download. And the origin substituted into the served copy is shell-quoted: a
    Host of a`id`b reaches URL.origin intact, and imagecheck sends 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_IMAGE decides — a registry-qualified ref pulls, a bare one builds
    — and deploy.sh prints which on every run. REEMOAT_CP_SOURCE overrides the
    derivation, and an unrecognised value is refused rather than defaulted.

    In pull mode CP_IMAGE_INPUTS does 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_fingerprint inspects the local image
    either way, so CP_IMAGE_MOVED and the RELAY_INPUTS rule 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.md said to put REEMOAT_CP_IMAGE=ghcr.io/… in the control
    plane's env file. It could not win: compose gives the shell environment
    precedence over --env-file for ${…} interpolation, and compose.sh
    exported its own default before compose ever ran — measured, compose.sh config with a registry ref in that file still printed
    image: reemoat/control-plane:current.

    The half with no symptom is worse. deploy.sh calls cp_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, and deploycheck
    asserts no script holds a second copy of the default.

  • GET /install.sh answers with the scheme a browser actually used.
    publicUrl takes it from socket.encrypted, and the service runs plain HTTP
    behind a proxy that terminates TLS — so it answered http://, and the plain
    form of a deployed origin is a 301 the installer deliberately does not
    follow. x-forwarded-proto is read, gated on trustedProxyHops exactly as
    callerAddressOf is. ⚠ The same defect on controlPlaneUrl (four
    code-minting routes) is known and not fixed here: changing publicUrl
    itself changes what every enrollment paste has said since the first release.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 05:45

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 in prune() 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_at moves 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_credentials or system_credentials now 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 plain DELETE. 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.md Q7.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 made POST /sessions answer a permanent 502 for 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.harnesses and contributes.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 https anywhere, or http to this machine or this
    network, which is what makes Ollama, vLLM and LM Studio reachable; where it is
    http the consent screen says the key travels in the clear.
    rends-east/reemoat-plugin-byo is 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
    says default.
    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 say signed 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...

Read more