52 KiB
Changelog
What each published build changes for you, newest first.
The three images are versioned independently and their numbers are not meant to match — usually only
one of them changed. An entry says which artefact and which version, so web 0.102.0 and
api 0.81.0 sitting under the same date is the ordinary case, not a mismatch.
Every version published to a channel appears here. Nothing is written from memory afterwards: a build cannot be published without its entry.
2026-08-28 — Models other than OpenAI's work again api 0.96.0
Read this if you point Jarvis at anything other than OpenAI. Self-hosted models, Qwen, Llama, Mistral, anything behind LiteLLM or a similar gateway. If you use OpenAI, nothing here changes for you.
A conversation in which the assistant had used a tool would fail from that point on, with the provider answering:
400 System message must be at the beginning
and failing the same way on every retry and every later message in that conversation. There was no way round it except starting a new conversation and avoiding tools, which is most of what Jarvis does.
The cause was on our side. Jarvis records what a tool returned as its own entry in the context, placed immediately after the assistant turn it belongs to — the position is what says "these are the results of the turn above". That entry was labelled as a system instruction, which OpenAI accepts anywhere in a conversation and almost every other provider rejects outright.
It is now labelled in a way every provider accepts at any position, and it still says in its own first sentence that the platform wrote it and the assistant did not — which is what stops a model learning to write tool results instead of asking for them. Nothing changes about what the model is told; only the envelope it arrives in.
A guard now also sits on the last line before any request leaves for your provider, so a mislabelled entry is corrected and logged rather than costing you a conversation.
Nothing to do. Take the build; conversations that were failing will work on their next message.
2026-08-27 — A machine could show Offline while you were using it api 0.95.0 web 0.121.0
Only affects deployments running more than one copy of the api. On Docker Compose this cannot happen and there is nothing here for you beyond taking the build.
An agent could sit in the list marked Offline while its last heartbeat was seconds old and a terminal was open on it. The machine was fine throughout — it was the row that was wrong, and it stayed wrong until that machine happened to reconnect, which for one that never drops is never.
It took a restart to start. When an api replica stops it closes its agents' sockets and records them as offline, which is true of the sockets it just closed. An agent notices immediately and reconnects to another replica in milliseconds, and that replica records it as online — then the stopping one's bookkeeping ran and overwrote it. Whichever machines happened to reconnect inside that window were left marked offline for good.
Two things changed so that it cannot stick. A replica shutting down now checks whether each machine has already been picked up elsewhere and leaves those alone. And a heartbeat arriving on a socket repairs a row that claims the machine is gone — so any window left over closes itself within the minute, without restarting anything.
A machine that is revoked or waiting for approval is never changed by this. Those are decisions you made, and a heartbeat is not one.
And the list no longer disagrees with the machine's own page. The page has always reported whether a socket is attached right now; the list printed the stored status instead, so the two could say different things about the same machine. When they disagree the list now leads with the live connection and marks the row as catching up. The opposite case is unchanged: a machine that claims to be online with no socket attached still reads Online · reconnecting, because there the stored value is the one over-promising and you want to see it.
2026-08-27 — Redis can survive losing a node api 0.94.0
Nothing to do on Docker Compose. Every setting below is optional and absent by default, and an unchanged stack behaves exactly as it did. This release is for deployments that run more than one copy of the api.
A Redis that fails over is now something you can point at. REDIS_URL names one server, and
there is nowhere in a URL to put what Redis Sentinel needs: the sentinels themselves, and the name
of the primary they are watching. So there are two new settings, and setting them switches the
shape — see .env.example:
REDIS_SENTINELS=a:26379,b:26379,c:26379
REDIS_SENTINEL_NAME=jarvis
Set one without the other and the api refuses to start and says why, rather than starting and
waiting forever for a primary nobody has heard of. REDIS_PASSWORD and REDIS_SENTINEL_PASSWORD
are there too, because the sentinels' password is frequently not the data node's. Redis Cluster
is deliberately not supported: the realtime adapter and the inter-pod bus both publish across what
would be different shards.
What a failover costs, measured rather than estimated: commands issued while the primary is gone queue and land on the new one, so a failover is a pause and not an error storm. Messages published during it are lost — Redis pub/sub has no buffer and no redelivery, and nothing on the client side can change that. In practice that is a few seconds in which something happening on one api replica may not reach another. Nothing is corrupted by it: what must survive is in Postgres for exactly this reason.
Losing Redis now appears in the log. Nothing was listening for connection errors on those sockets, which meant a Redis blip printed dozens of stack traces per container, none of which said what had broken or that it had come back. It is now one line when a connection drops and one when it returns, naming which connection and how long it took.
For Kubernetes: the secrets can now be minted by the cluster. Generating them exactly once used
to depend on every container sharing one filesystem, which no longer exists once there are several
pods — so it was a manual step that silently corrupted the vault if you got it wrong. There is now a
Job that mints them once, using the API server's refusal to create something twice as the
guarantee. It can create a Secret and cannot read one back. See deploy/kubernetes/.
Also in deploy/kubernetes/, which has now been applied to a real cluster rather than only
validated: manifests for a Sentinel-backed Redis, replicas that are spread across nodes instead of
being allowed to share one, and two faults that stopped every pod in the directory from starting
under the restricted Pod Security Standard. If you have been working from those files, take the
new ones.
2026-08-27 — The console container listens on 8080 and runs unprivileged web 0.120.0
Check your port mapping before you pull. The web container used to listen on 80 and now
listens on 8080, because binding a port below 1024 needs root and the container no longer runs
as root. The compose file in this repository is already updated — ${JARVIS_PORT:-8080}:8080 — so
if you use it unchanged there is nothing to do. If you wrote your own mapping, or your reverse proxy
targets the container port directly rather than the published one, change :80 to :8080.
Nothing else about how you reach it changes: same URL, same paths, same TLS terminator in front.
The container's upstream is configurable. JARVIS_API_UPSTREAM and JARVIS_DNS_RESOLVER now
say where the API is and who resolves that name; they default to what compose has always used, so an
unchanged stack behaves identically. They exist for anybody running these images somewhere the name
api and Docker's own DNS do not mean anything.
A terminal no longer opens a second shell when your connection drops. Reconnecting used to silently start a fresh login on the machine — a new session, a new audit entry, a new recording — underneath a window still showing the old scrollback. It now tells you the connection was lost and offers to open a new session, which is a decision rather than something that happened to you.
A part of the console that fails to load after an upgrade now says so. The interface ships as content-hashed files that are replaced on every upgrade, so a tab left open from before can reach for something the server no longer has — which used to be a blank panel and a line in the browser console. It now raises the same notice an update raises, with words, and offers the reload that fixes it.
2026-08-27 — An upgrade refuses to drop a column instead of doing it api 0.93.0
The one to read. Until now, every boot ran the schema sync with --accept-data-loss, so an
upgrade that needed to remove a column simply removed it. That was survivable while one container
was the whole deployment and is the wrong default for anything else — and it meant a destructive
schema change happened with nobody having decided anything.
It now refuses, prints which columns or tables it would have dropped, and stops. Nothing is
altered and the api does not start. If those drops are what you want, set
SCHEMA_ACCEPT_DATA_LOSS=true in .env for that one upgrade and put it back afterwards. Take a
dump first either way: there is still no migration history, so pulling an older image does not put
the schema back.
The schema phase is also its own entrypoint now (migrate.sh) rather than part of the api's boot.
On compose nothing changes — the api still runs it, because one container is the whole deployment —
and it takes a database lock while it works, so --scale api=2 waits rather than races.
Two health endpoints, for anything watching this instance. /api/health/live says only that the
process is running, and /api/health/ready answers 503 when Postgres or Redis is unreachable.
/api/health is unchanged, including its 200-while-degraded behaviour, so whatever you already
point at it keeps working.
Two tabs no longer sign you out of everything. Both tabs share one session cookie, so both refreshing at the same moment looked exactly like a stolen cookie being replayed, and the protection against that revoked every session you had. Concurrent refreshes are now told apart from a replay by how long ago the token was rotated. A genuine replay still revokes the family, which is what it is for.
An enrolment token's use limit is now actually a limit. Two machines running the same install command in the same second could both enrol on a token that permitted one — an Ansible play across a fleet does exactly that. The check and the increment are one operation now.
A terminal transcript survives the process being killed. Output used to live only in memory until the session ended, so an out-of-memory kill or a host losing power took the whole recording of a root shell with it. It is written down every few seconds now, and a session that ended that way hands back what was captured, clearly marked as stopping early. A session with no transcript at all also says which of the three reasons applies rather than guessing — recording was off, retention has passed, or it was lost — because telling an auditor a recording was never made when it was made and lost is the answer that stops them looking.
A restart no longer writes an ending into a session that is still open. Starting up used to close every terminal session row it found, on the reasoning that a row with no ending belongs to a process that died. With one container that was true. It is now scoped to the process's own sessions.
The api container runs as a non-root user. Nothing you do changes; it matters if you run these images under a policy that refuses root containers.
Optional: VAULT_KEY_CREATED_AT. Only needed where the vault key file is rewritten by your
platform rather than sitting still in a volume — on compose it is inferred correctly from the file
and you can ignore this. See .env.example for what it decides.
And the reason for most of the above: more than one api replica is now possible. It is not yet supported, and this compose file still runs one. Nobody has served real traffic on more than one, so if you are considering it, read the note in the README first.
2026-08-25 — A run gets 200 tool rounds instead of 50 api 0.92.0
If the assistant has been stopping mid-investigation with "the results above were not analysed", this is why. A run that reads a config, checks a service, greps a log and correlates two hosts spends its budget on legitimate steps and then hits a ceiling meant for runaway loops. Asking it to continue re-sends the whole conversation and pays for the same work twice, which is the expensive outcome the ceiling was supposed to prevent.
The ceiling exists to stop a model that has stopped making progress, and at that job 200 is as good as 50 — a loop that is genuinely stuck is stuck within a few rounds. Nothing else about how a run is governed changes: you still watch it, the autonomy level still gates every write, and anything consequential still asks for approval.
One thing to expect on very long runs. With more rounds available, the likelier ending is now the model's context window rather than the round limit. That has always been reported plainly — "this conversation has outgrown the model's context window" — and a run that reaches it should be started again as a fresh conversation rather than continued.
2026-08-25 — The terminal button is on the row web 0.119.0
The terminal added in the entry below was in the row's overflow menu, behind Test, Connections and Edit. Two of us went looking for it on the Assets screen and neither found it, which is a placement problem rather than an attention one.
It is now the first icon on the row, and it names the route it will take. A machine reachable two ways shows one icon for its preferred route and keeps the other as a named entry in the menu — two identical terminal icons side by side would stand for two different networks and two different logins, and an icon cannot say which is which.
The row still carries three icons rather than four, so Edit moves into the menu on any machine that has a route. Test and Connections keep their place: they are what you reach for when a route does not answer, which is the same moment you were reaching for the terminal.
2026-08-25 — A terminal goes over a route you choose api 0.91.0 web 0.118.0
Opening a shell used to mean opening it through the Jarvis agent, from the Agents page. A Linux host commonly answers on OpenSSH and through an enrolled agent, and those are different networks, different credentials and different accounts at the other end — so which one your shell goes over is a decision. Until now it was made for you by whichever was available.
You can now open a terminal from the Assets list as well, and pick the route by name. A machine with one way in opens straight away; one with several offers an entry per route. The choice is made before the shell opens, and it is recorded — on the session, in the audit trail, and in Settings → Organization → Terminal sessions, which now shows which route each session used.
SSH sessions are held to exactly the same rules as agent ones. The same idle timeout, the same
four-hour ceiling, the same output limit, the same recording under VAULT_MASTER_KEY and the same
retention policy. A different route is not a different set of rules.
What decides who may do this has not changed. The per-machine "may open a terminal" permission is the same one, and it still has no bearing on what the assistant may do on those machines. What it now permits is a shell by any route that machine has, and the access editor says so where you grant it.
Fixed
- The agents list offered a terminal on a machine whose agent had no route recorded, which could never open. It now says why instead.
2026-08-25 — Settled approval requests fold away web 0.117.0
A conversation with several approvals behind it was a wall of text. Each settled request kept its full plan on screen — a summary, every numbered step and the machines it touched — so a thread with six of them was a page you scrolled past rather than read. What you actually want from that list is which plan it was, whether it went ahead, and who said so.
Decided requests now start folded and show exactly that. Open one to read its plan again; it is all still there, unchanged.
A request still awaiting your decision does not fold. Its plan is the thing you are being asked to agree to, and a row you have to open before you can see what you are approving is a row that makes not looking the easy path. Those stay open, as they were.
One small thing on top: if somebody else decides a request while you have it open, it stays open rather than folding up mid-sentence.
Fixed
- On a phone, an approval's title was squeezed into the space three badges left over — often one word per line, with the status badge across it. The title now takes a row of its own on narrow screens.
2026-08-24 — The diagram full-screen button explains itself web 0.116.0
If full screen has been missing from your diagram viewer, the button was hidden rather than broken. It hides itself when the browser says it will not grant full screen, which is right on iOS — Safari does full screen for video and nothing else there — and wrong on a desktop whose browser is centrally managed, where a policy can forbid it. The result was the same either way: the control simply was not there, with nothing to say the browser had taken it away rather than us.
It is now shown and disabled on any pointer device, with a tooltip naming the likely reason. The expand control beside it still fills the dialog, which is the best any browser can offer once full screen is off the table.
And when the browser refuses a full-screen request outright, it says so. That rejection used to be swallowed, so a click did nothing at all and no message anywhere explained why. The browser's own sentence is what you see now.
Fixed
- A diagram viewer could get stuck believing it was full screen when it was not — the button then offered to leave a full screen that did not exist, and clicking it did nothing. The comparison behind it treated "no full-screen element" and "no diagram attached" as the same thing.
2026-08-24 — Two places still described the switch we had just removed web 0.115.0
The entry above says JARVIS_TELEMETRY was removed "from the compose file, from .env.example,
from the licence screen and from the install wizard". Three of those four were true. The licence
screen still carried a line telling you to set JARVIS_TELEMETRY=off in .env to stop reporting,
in all three languages, and this README's community-edition table still listed reporting as
"Switchable off". Both were wrong the moment that release shipped, and both are corrected here.
Nothing about the behaviour changes — there was never a switch behind those sentences to begin with. What changes is that the product and this document now say the same thing: every instance reports, the fields are listed below and on your own licence screen, and there is nothing to set.
2026-08-24 — Reporting is no longer optional api 0.90.0 web 0.114.0
JARVIS_TELEMETRY is gone, and with it the ability to stop your instance checking in. Three days
ago this repository told you the variable worked, and apologised for the fact it had not. We are
withdrawing the option rather than leaving you to discover it stopped working: it is removed from the
compose file, from .env.example, from the licence screen and from the install wizard, which is
nine steps now instead of ten.
The reason is the one already written down for why a community instance reports at all. A free tier the publisher cannot see is a free tier nobody can warn — and the instances that opt out are exactly the ones most likely to be running an old build unattended, which is the population a warning is for.
What has not changed is what leaves your network, or your ability to see it. The field list below is complete and unchanged: your instance id and public key, the version and contract hash it runs, its public address, and counts of organizations, users, agents and assets. No names, no conversation content, nothing about the machines you administer. Settings → Licence shows you the address and the contents resolved by your own running instance, which is the answer that matters rather than anything this document says.
If that does not suit your deployment, tell us before you deploy — we would rather have the conversation than have you find out from a packet capture.
Also in this release
The sign-in field is off on a new installation. The orb you press and hold to reveal the sign-in form is a deliberate piece of the product, and it is no longer what a brand-new instance greets you with — a first-time deployer meeting an unlabelled glowing hexagon has a puzzle rather than a login. Turn it on under Settings → Platform → Sign-in; instances that already have it keep it.
2026-08-24 — "Open Jarvis" sent you back to the install screen web 0.113.0
If you installed on web 0.112.0, the last click of the wizard put you in a loop. You finished, pressed Open Jarvis, and landed back on the install screen reading "This instance already has an administrator" — with a button to sign in that bounced you to the console, which sent you back to the install screen again. Nothing was wrong with your instance and nothing was lost; a reload of the page was enough to break out of it. It was still the worst possible moment for it, on the one screen a new deployer meets exactly once.
The console asks "does this instance need installing" once, when the page first loads, and it had no way to learn the answer had changed under it — so at the end of the wizard it was still acting on what it had been told before you started. The wizard now tells it.
Upgrade with the usual two lines:
docker compose pull
docker compose up -d
2026-08-24 — The claim no longer expires api 0.89.0 web 0.112.0
The thirty-minute window on the install screen is gone. There is no countdown, no "the window has closed", and no more restarting the api container to open another one. The first account created still owns the instance; it can now be created whenever you get to it.
Which means the exposure is yours to manage, and we would rather say so than let a timer imply it.
Until somebody creates that first account, anyone who reaches this instance can. The gap that matters
is between docker compose up -d and you opening a browser — so keep it short, and prefer pointing
a public hostname at the instance after you have installed rather than before.
Everything else about the claim is unchanged, including the part that protects you where a timer never did: an instance whose database has gone missing still costs the last characters of your vault master key to claim, because from the outside it is indistinguishable from a new one.
2026-08-24 — Losing your database no longer puts your instance up for grabs api 0.88.0 web 0.111.0
If your database is ever lost or replaced, your instance was claimable by whoever loaded the page first. From the outside it looked exactly like a brand-new deployment — no accounts, no completed setup — so the install screen opened its 30-minute window and offered a super-admin account. Except it was not a new deployment: it was yours, on a hostname already in DNS, already in people's browser history, and already dialled by every agent you have enrolled.
We found this the way these things get found. A docker compose down -v meant for a throwaway test
stack landed on a production one, because the compose file that had been copied into that directory
carried its own project name. The database went. What was left was an instance offering itself to the
internet.
What changes. An instance that cannot prove it is new still opens the window — you need a way back
in — but creating the first administrator now costs the last characters of your vault master key.
You have it, in your .env or on the jarvis_secrets volume you were told to back up separately.
Nobody else does.
A genuine first installation is never asked for it, and neither is a restart during a slow one. The test is the age of the vault key at the moment your database first saw it: seconds on a real first boot, months on a replacement. It is recorded once, so restarting the api to re-open a window does not change the answer.
Nothing to do. No new variable, no migration step. Instances that upgrade into this keep working exactly as they are — the rule only ever applies to an instance with an empty database, which yours is not.
2026-08-24 — A friendlier install, and a vault key you can only be handed once api 0.87.0 web 0.110.0
The install screen leads with Jarvis now. The mark, the name and a welcome come before anything is asked of you — the first thing this product ever showed a new deployer used to be a password field with no explanation attached. Ten steps, about five minutes, and the road map on the first screen is honest about which ones you can skip.
Only the address really needs you. The model step used to refuse to continue without a model id and a key, which trapped anybody evaluating Jarvis without a provider key in hand — on a product whose remote terminal, agent fleet and vault all work perfectly well without one. An untouched form now continues and saves nothing. A half-filled one still refuses: that is a mistake rather than a decision.
The wizard now asks who may create an account. Self-registration defaults to open, and nothing in the install had ever mentioned it — so an instance on a public hostname would accept an account from anybody who found it, the moment you finished. It is a switch on the address step, beside the field that decides who can reach the sign-in page at all. Change it later under Settings → Platform → Sign-in.
The vault key is shown, and downloadable, exactly once. The install screen hands you the file
rather than telling you to go and run docker compose exec — that command is a wall for anybody
deploying through Coolify, Portainer or a managed host, and what is behind the wall is the one value
no backup can reconstruct. The screen says so before you click. Afterwards, and any time later, the
key is on the host:
docker compose exec api cat /var/lib/jarvis/secrets/vault-master-key
Fixed
- An interrupted install could not be finished. Coming back to the wizard after the organization step showed you an empty form and demanded a name, and the request behind it could only fail — the community edition allows one organization and you already had it. There was no way forward and no way around. It now recognises the organization you already have and moves on.
- A first boot with no configuration printed a red
ERRORaboutJARVIS_UNATTENDEDon instances where it was not set. Cosmetic, but it was the first thing a new deployer read indocker compose logs. JARVIS_ADMIN_EMAILandJARVIS_ADMIN_PASSWORDalone would install the instance silently, with noJARVIS_UNATTENDED=on, and skip the wizard entirely. Those variables are what an unattended install needs in addition to the flag, never a second way of asking for one. If you keep them in a shared shell or a fleet template, this is the release where they stop acting on their own.- Installing in German or French left you in an English console — the account the wizard creates now records the language you installed in.
Changed
JWT_ACCESS_TTLandJWT_REFRESH_TTLare read from.envlike every other tuning value instead of being fixed in the compose file..env.exampledocuments what each actually does — the refresh one is an idle timeout, not the session lifetime its name suggests.PUBLIC_URLis gone. It was a second variable for the same address asWEB_ORIGINand had not been read by anything for two releases; it is the stored instance address now, on a settings screen. Remove it from your.envif it is still there — nothing breaks either way.
New in the manual
The published README grew the procedures it was missing: restoring the vault key onto a new host
(do it before the first up -d — a generated key is never overwritten, so bringing the stack up
first makes yours unusable), getting back in when you are locked out of the only administrator
account, moving an instance to a new address and what that does to enrolled agents, and which
generated secrets can be rotated.
2026-08-24 — JARVIS_TELEMETRY=off did not work, and now does
If you set JARVIS_TELEMETRY=off in .env, your instance has been reporting anyway. We are
sorry. The variable never reached the container: compose reads .env to fill in ${...}
placeholders inside the compose file, it does not hand that file to the services, and the api
service never listed this one. So the API saw nothing, applied its documented default of "on", and
checked in — while the README, the changelog and .env.example all told you that one line was
enough.
What was sent is what the README has always listed and nothing more: your instance id and public key, the version and contract hash it runs, its public address, and counts of organizations, users, agents and assets. No names, no conversation content, nothing about the machines you administer.
The fix is in the compose file, not in the application, so pulling this repository again is the whole of it:
git pull # or re-download docker-compose.yml
docker compose up -d
Then confirm it took, from the instance itself:
docker compose exec api printenv JARVIS_TELEMETRY
No output means the fix has not landed yet; off means you are silent. Settings → Licence says
the same thing on screen, and that is the answer to trust — it reads what the API actually resolved
rather than what a file claims.
If you would like the entries a silenced instance should never have created removed from our side, write to antoine@luxit.be and quote the instance id from that screen.
web 0.109.0 — 2026-08-24
The welcome now comes before the account. The very first thing anybody saw of Jarvis was a form asking for a password — no name, no explanation, no idea what they were about to be given administrator rights over. The introduction needs no account, so it goes first, and creating the administrator is step two with a way back to it.
Mail and the licence are two steps. They shared a page and have nothing to do with each other — one is an app registration in your Entra tenant, the other a key from your supplier — which made one long screen out of two short questions. The licence step now leads with the thing most people need to hear: you almost certainly do not need a key, and the community edition is perpetual.
web 0.108.0 — 2026-08-24
The install opens on a welcome rather than on a checklist. It used to begin with "Checks: database, Redis, vault key" — which tells somebody who has just deployed a product they have never run that they are already in the middle of something. The first screen now introduces Jarvis, says what the next steps will ask for, roughly how long it takes, and which of them you can skip.
The checks moved to the end, and that is a correction rather than a preference. Run first, they had nothing to look at: your public address had not been chosen, so there was no URL to probe and no hostname to judge security keys against — three of the six findings were about a deployment that did not exist yet. They now run immediately before the screen that commits the install, against the one you have just described.
Reporting has a step of its own. It shared a page with mail and the licence, where a question about what leaves your network sat underneath two forms. It is the only decision in the wizard we benefit from, which is exactly why it gets a page you cannot scroll past.
The step markers no longer collide. Eight labels never fit the wizard column at any screen width and ran into each other. The strip is markers now — filled where you are, ticked behind you — with the step named in full underneath and the whole road laid out on the welcome screen.
api 0.86.0 · web 0.107.0 — 2026-08-24
Mail and the licence are configured IN the wizard now, not somewhere else. That step used to show two paragraphs whose only affordance was a link to the settings screens — and those links could not work: an instance that has not finished installing sends every other address back to the install screen, so clicking one opened a tab that bounced straight back. The real forms are on the step now, the same ones you meet under Settings afterwards, and nothing on it is required.
You can decide there whether this instance reports to us. Reporting used to be an environment variable and nothing else, so a fresh install had no way to answer the question without editing a compose file. It is a switch on that step and on Settings → Licence.
JARVIS_TELEMETRY=off keeps its authority: an instance whose host has switched reporting off shows
the control locked and says why. Deciding what leaves your network stays with whoever runs the host,
which is exactly why it was a variable in the first place — the switch adds the case where the person
installing the instance IS that person, and the case where you want to fall silent later without a
restart.
Your vault key is a download. The last step used to print docker compose exec api cat … and ask
you to go and run it. If you deployed through Coolify, Portainer or a managed host, that was a wall
in front of the one thing here that no backup can reconstruct. Now the key is shown on the page with
a button that saves it as a file, and the command is still underneath for anyone who would rather
take it off the host.
That view exists only while the install is unfinished. Once you press Finish it is refused for good, and every time it was used is in the audit trail.
Unattended installation, for fleets and for CI. Set JARVIS_UNATTENDED=on with
JARVIS_ADMIN_EMAIL and JARVIS_ADMIN_PASSWORD and the instance installs itself at boot from the
variables you already provide, and never shows the wizard. Everything else stays optional.
If those credentials are missing or the password is under twelve characters it says so loudly and leaves the install UNFINISHED rather than completing. An instance marked installed with no account is one nobody can ever get into, and no restart recovers it — falling through to the ordinary install screen is strictly better.
web 0.106.3 — 2026-08-24
The install screen tells you where you are. Its step list was six labels in a row where the current one differed by a font weight — and the difference did not even arrive, because the class meant to dim the others named a colour this console does not define. Every label rendered identically, so the one screen whose whole job is to walk you through six steps could not say which of them you were on.
The stepper now carries that three ways at once, none of them colour alone: a tick for what is done and a filled number for where you are, connectors that fill in behind you, and a line that names the step — "Step 3 of 6 · The model" — which is also what makes it readable on a phone, where the labels step aside.
The same missing colour had flattened the rest of the wizard: hints and explanations rendered as bright as the headings above them, error messages were not red, and the boxes around the warnings had no edge. All of it reads properly now.
api 0.85.3 — 2026-08-24
An instance still being installed no longer reports itself. It used to check in thirty seconds
after boot, before anybody had chosen its address — so a fresh install announced itself as
http://localhost:3000, with every count at zero. That is worse than silence: the address exists in
the report so that your provider can tell one installation from another, and a list of identical
localhost entries answers nothing.
It now waits until the install screen is finished, then reports normally with the address you chose.
Nothing is lost, only deferred. JARVIS_TELEMETRY=off still silences it entirely.
web 0.106.2 — 2026-08-23
A language picker on the install screen. Every other page that can be reached without an account has one — the sign-in page, a shared transcript — and the install wizard, which is the longest piece of reading this product puts in front of somebody who has not yet decided to trust it, did not.
It follows your browser as before; the picker is for the case the browser is wrong, which is routinely: installing from a colleague's laptop, or on a server whose locale nobody set. Before the first account exists the choice is remembered in this browser; after it, it is saved to your account as well — so it also corrects the language the claim guessed.
api 0.85.2 · web 0.106.1 — 2026-08-23
Installing in German now ends in a German console. The install screen has always followed your browser, in English, French or German — but the account it created took the database default of English, and the console adopted that the moment the wizard handed over. Somebody who had just read seven screens in their own language was greeted in another and left to find the language switcher.
The account now records the language the wizard was read in. Change it whenever you like under Settings → Profile; this only decides where you start.
api 0.85.1 — 2026-08-23
A fresh install no longer stores an address nobody chose. api 0.85.0 wrote
http://localhost:3000 as the public address of any instance that started with no configuration —
the schema default for WEB_ORIGIN, saved as though somebody had decided it. The boot banner then
told an operator on a remote host to open a URL that leads nowhere.
The address is now left unset until you choose it, which is what the install screen already assumed:
it fills the field from the address you are reading it at, and the checks say plainly that nothing is
set yet. The banner names the path and not the host — you know how you reach the machine, you were
only missing /install.
Nothing to do if you set WEB_ORIGIN yourself: it is still read, once, exactly as before.
api 0.85.0 · web 0.106.0 — 2026-08-23
Installing Jarvis no longer starts with a text editor.
docker compose up -d
Then open it in a browser. An install screen creates the first administrator, checks the deployment
and asks for the rest. The .env step is gone — it demanded six values before the stack would
start, four of them secrets you had to generate with openssl, and the whole file is optional now.
A wrong model key is a message instead of a crash loop. This is the change underneath all the
others. The api validated its configuration at boot and called process.exit(1) when anything was
missing — so a mistyped key, an account out of credit or an endpoint that had moved presented as a
container that would not stay up, with the actual reason on line 40 of a log you had no reason to
open. An unconfigured instance now starts, says what it needs, and the model screen has a test
button that asks the model for a token before saving, showing the provider's own words when it
refuses.
A brand-new instance can be claimed for 30 minutes, and this closes a real hole. Jarvis used to
accept exactly one registration on an instance with no users and make that account super-admin — so
a freshly deployed instance reachable from the internet belonged to whoever found it first, on a
product that holds SSH keys and opens shells on your clients' servers. The install screen shows a
countdown; docker compose restart api opens another window if you miss it.
Everything the wizard asks has a permanent screen. Settings → Platform gained two tabs: Model — endpoint, key, model and thinking level, with the same test button the wizard uses — and Address, for your public address and proxy-hop count. The wizard is a first-run convenience, not the only way in: an instance whose OpenAI key has been rotated, or whose hostname has moved, says so on a settings page rather than being re-installed.
The proxy setting is now something you are shown rather than asked to guess. TRUST_PROXY_HOPS
decided which address landed in your audit trail, and nobody can know it in advance. The install
screen displays the X-Forwarded-For chain your own request actually carried and the address the
API resolved from it, and you confirm what you see.
Your secrets are generated for you, into a new jarvis_secrets volume. Back it up, separately
from the database — the vault key lives there and nowhere else, and a database backup holds only
ciphertext. The install screen will not finish until you have read the key and typed its last
characters back. The key never leaves the server: there is deliberately no endpoint that returns it.
Upgrading from an earlier build? There is nothing to do
Leave your .env exactly as it is. Your secrets are adopted into the new volume rather than
replaced, so the vault is untouched. WEB_ORIGIN becomes the stored public address, your OPENAI_*
values become the stored model settings, and an instance that already has an administrator is marked
installed by a backfill — you will never see the install screen. Everything those variables used
to govern is now a settings page; the variables still seed a fresh install and are otherwise ignored.
One thing did change for you: PUBLIC_URL is no longer read. It was a second variable for the
same address. There is one now, seeded from WEB_ORIGIN and edited under Settings → Platform.
Changing your public address orphans enrolled passkeys. It always did — a passkey is bound to the hostname it was created under — but it was previously a variable nobody edited twice. Now that it is a field on a screen, the screen warns you before it saves.
api 0.84.0 · web 0.105.0 — 2026-08-21
Ask for a licence from inside Jarvis. Settings → Licence has a form: your company, somebody to answer, roughly what you need. Send it and your provider gets it with your instance's version and counts attached, so the conversation starts at "here is what fits" instead of "how many machines do you have".
An approved licence installs itself. The request is signed by your installation, so the licence is issued for that exact deployment and arrives on it at the next report — no key to copy, nothing to paste, and no way to paste it into the wrong instance. You get the key by email as well, for your records and in case you rebuilt the host while you waited. A decline comes back the same way, with your provider's reason on the licence screen and in your inbox. You can withdraw a request you no longer want, and ask again whenever something changes.
Community instances now report, and this reverses what we told you this morning. api 0.83.0 said
— here, and in the README — that an instance without a licence contacts nobody at all. That is no
longer true. A community instance now sends the same fields a licensed one does: counts, the version
it runs, its contract checksum and its public address, to checkin.luxit.be. No names, no
conversation content, nothing about what you administer; the README lists every field, and it is the
same list as before.
Why: we could not tell whether the community edition had reached anybody, or which builds were running when we needed to warn people about one — and the request form above needs the same channel to work at all.
JARVIS_TELEMETRY=off in .env stops it completely, and costs you nothing else: every
community allowance, every feature and every connected machine stay exactly as they are. What you
lose is the licence request form, which has nowhere to send. A licensed instance reports regardless —
that has always been part of what a licence is, and the licence screen says so.
If you would rather not report, set that variable before you upgrade. Nothing is sent until the instance's first check-in after start-up.
On the licence screen: what leaves your network, the exact address it goes to, and whether reporting is on at all — all three visible without opening a compose file.
api 0.83.0 · web 0.104.0 — 2026-08-21
Jarvis no longer needs a licence key to be useful. An instance without one now runs the community edition — 1 organization, 3 users, 5 agents, 10 assets — perpetually, free, with every feature working and nothing reported to anybody. Install it, connect it to something real, and see what it does before there is anyone to talk to. A licence raises those limits; it does not switch the product on.
This replaces the behaviour of the last three days, where an instance with no key came up, accepted its first administrator account and then refused everything else. If you installed Jarvis and found it would not let you create anything, this is the release that fixes it — upgrade, and the allowances above apply immediately. Nothing needs to be reset, re-run or re-entered.
A licence that stops applying now falls back to the same allowances, instead of refusing every creation. Expired, withdrawn, or a key that will not verify: your instance keeps running everything already set up and may still create up to the community numbers. There is no state left in which Jarvis stops being usable over a billing question.
Fixed: an unreadable licence key blocked every creation. The licence screen said nothing was being withheld, and something was — a key that lost a character in a paste resolved every limit to zero. It now falls back to the community allowances, which is what the screen always claimed.
On the licence screen, a fresh install no longer shows a red "No licence" badge. It says Community edition, lists what that allows against what you use, and states plainly that nothing is reported. The refusal you get when a limit is full names the edition and what raises it.
Nothing else changes. No configuration, no migration, no new variable — JARVIS_LICENSE_KEY is now
documented as optional, and it always could be left empty.
api 0.82.0 · web 0.103.0 · agent 0.27.0 — 2026-08-21
Nothing changes in what these builds do. They are identical in behaviour to api 0.81.0, web 0.102.0 and agent 0.26.0, rebuilt so that every published image carries the same set of registry labels: what the image is, where its documentation lives, and a link to this file. The package page in the registry now says something useful to somebody who lands on it without context.
Upgrading is worthwhile only for that. If you are running the previous three and reading this, you already have everything they do.
The earlier version tags have been removed from the registry. Every build before these three
is gone; the stable, dev and latest channels point at them. If you had pinned an exact
older version in .env, that pin no longer resolves and docker compose pull will fail — pin
one of the versions above instead, or drop the pin to follow stable.
api 0.81.0 — 2026-08-21
Approving a plan now actually authorises it. When Jarvis asks for consent on a whole plan and you approve it, the steps that touch the machines the plan names are no longer confirmed one at a time. Previously the approval bound nothing at all — the assistant was asked to honour it and the executor never checked — so an approved plan still stopped at every step, and a read-only conversation could be granted a plan it had no way to carry out.
The clearance is deliberately narrow. It applies only in the conversation the plan was raised in, only to the machines the plan names, only up to a risk ceiling, and only for four hours. Anything outside it still asks. It can never be wider than the approver's own access to those machines.
Two ways somebody could get more than they should, closed.
- Approving used to lend the approver's own permissions to the plan for the rest of the turn. Work now runs as whoever asked for it, which is what the audit trail already claimed.
- Deciding a plan asked nothing about whether the decider may touch the machines it names — the per-command confirmation had checked this for months and this path had not. It does now, for every machine in the plan. Refusing is never restricted: anyone who can see a plan is wrong may stop it.
Wording that was not true. A command that was waiting for a human when the server restarted used to be reported as possibly having taken effect, and told you to go and check the machine. It never ran — the confirmation happens before anything is sent — so it now says so.
If you have approval requests older than this release, they are recorded as expired. They were made under the old behaviour, where approving authorised nothing; treating them as live permissions would grant something nobody was asked for.
web 0.102.0 — 2026-08-21
The approval panel says what approving does. It used to promise "Jarvis runs the steps below" and "Nothing runs until you decide". Neither was true: a pending plan blocks nothing, and approving did not run anything. It now describes the clearance you are actually granting — which machines, what ceiling, how long — and the rejection notice no longer claims that nothing ran.
The per-command confirmation is unchanged. That one does hold the command until you answer.
api 0.80.0 · web 0.101.0 — 2026-08-21
The footer says which channel this instance follows, beside the version numbers, when you set
JARVIS_CHANNEL in .env. Leave it empty if you pin exact versions — you then follow no channel,
and the footer shows nothing rather than a label that stopped being true.
api 0.78.2 · web 0.100.2 — 2026-08-21
Distribution channels. Images now carry stable and dev tags as well as their version.
stable moves only after a build has run on the publisher's own instance; dev moves on every
build. latest is a second name for stable, so nothing that already used it has to change, and
the compose files default to stable.
A channel is a pointer and a version is a fact. Promotion copies the manifest of an image that has already been published and run — it never rebuilds — so the bytes you receive are the bytes that were tested, not a fresh build of the same source.
Fixed: some builds reported their version as dev. The version is stamped into the image and
shown in the footer; a build published without it said dev while being tagged, pinned and run
correctly. Affected api 0.78.0 and 0.78.1 and web 0.100.0 and 0.100.1.
web 0.100.0 — 2026-08-21
Recorded terminal sessions can be replayed at the pace they happened, not only read as a finished transcript. Idle time is compressed so a session that was mostly waiting is watchable, and how much was skipped is stated. Sessions recorded before this release can still be read; they carry no timing, so they cannot be replayed, and the panel says so rather than hiding the control.
agent 0.26.0 — 2026-08-21
The command is now jarvis. jarvis-agent keeps working and will continue to: both names are
present on every machine, and an agent that updates itself gives itself the missing one. Uninstalling
is a verb — jarvis uninstall — beside status, start and stop. The old
jarvis-agent-uninstall command is unchanged.
jarvis uninstall refuses to run from inside a Jarvis terminal on the machine it is removing.
Stopping the service would kill the command mid-way, leaving the agent stopped and nothing
uninstalled, with no connection left to fix it through. Run it from an ordinary SSH session.
agent 0.25.0 — 2026-08-21
Internal: the agent asks the machine which name its service is registered under rather than assuming one. No visible change; it is what makes a later rename survivable.
agent 0.24.0 — 2026-08-21
Remote terminal on macOS. Interactive shells now work on managed Macs, as they already did on Linux and Windows. The session opens a login shell so the PATH matches what an administrator expects.
Fixed: sessions were recorded as "closed" instead of "exited". Typing exit ended the session
correctly but filed it as though it had been taken away, with no exit code. Sessions from before this
release keep the reason they were given.
Fixed: a file descriptor and a thread leaked on every clean exit. Only visible on machines that stay up for a long time with many sessions.