867 lines
52 KiB
Markdown
867 lines
52 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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 `ERROR` about `JARVIS_UNATTENDED`** on
|
|
instances where it was not set. Cosmetic, but it was the first thing a new deployer read in
|
|
`docker compose logs`.
|
|
- **`JARVIS_ADMIN_EMAIL` and `JARVIS_ADMIN_PASSWORD` alone would install the instance silently**,
|
|
with no `JARVIS_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_TTL` and `JWT_REFRESH_TTL` are read from `.env` like every other tuning value
|
|
instead of being fixed in the compose file. `.env.example` documents what each actually does —
|
|
the refresh one is an idle timeout, not the session lifetime its name suggests.
|
|
- `PUBLIC_URL` is gone. It was a second variable for the same address as `WEB_ORIGIN` and had not
|
|
been read by anything for two releases; it is the stored instance address now, on a settings
|
|
screen. Remove it from your `.env` if 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:
|
|
|
|
```sh
|
|
git pull # or re-download docker-compose.yml
|
|
docker compose up -d
|
|
```
|
|
|
|
Then confirm it took, from the instance itself:
|
|
|
|
```sh
|
|
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.**
|
|
|
|
```sh
|
|
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.
|