1463 lines
90 KiB
Markdown
1463 lines
90 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-09-08 — "Last active" told you someone had been seen, not that they had said anything `api 0.108.0` `web 0.131.0`
|
|
|
|
The per-user table on the Statistics page counted what each account had written, and showed when that
|
|
account was last active. Those two are further apart than they look. Being active is bumped by any use
|
|
of the product at all, so an account opened every morning to read reports that it was here today —
|
|
and says nothing about whether the person has typed since March. On a screen whose job is to show who
|
|
is actually using the instance, that gap is usually the thing being looked for.
|
|
|
|
**A "Last message" column**, sitting next to the message count it belongs to: when each account last
|
|
wrote, or "Never" if it never has. On a narrow screen it becomes its own labelled line on the card,
|
|
rather than a second bare "3 days ago" beside the first one, which would have named neither.
|
|
|
|
**This one column does not sort**, and the reason is worth stating rather than hiding. Every other
|
|
column there is ordered by the database while it selects the page; this one is worked out afterwards,
|
|
for the fifty accounts that page already chose. That is what keeps it from costing anything on a large
|
|
instance, and it is also what leaves the server nothing to order by. "Last active" beside it still
|
|
sorts, and answers the same question more coarsely.
|
|
|
|
**Self-hosters:** one index on the messages table is replaced by a wider one leading on the same
|
|
column — the old one is dropped and the new one built in its place, automatically when the api starts,
|
|
like every schema change here. No column is dropped and nothing becomes unique, so the schema guard
|
|
has nothing to refuse. On an instance with millions of messages the build is not instantaneous, and
|
|
the api finishes starting when the index finishes.
|
|
|
|
## 2026-09-08 — Nobody running Jarvis could tell how much it was actually being used `api 0.107.0` `web 0.130.0`
|
|
|
|
A platform administrator had no way to answer "is this instance quiet or busy, growing or stalled,
|
|
used by three people or by thirty" — only the individual screens for organizations, conversations and
|
|
agents, none of them a total, none of them over time.
|
|
|
|
**A new "Statistics" page**, under Settings, visible only to a platform administrator. It shows the
|
|
instance as a whole: how many users, organizations, assets and agents exist right now, how many
|
|
conversations and messages have been sent, and how each breaks down — assets by type, agents by
|
|
status, messages by who sent them.
|
|
|
|
**Trends, not just totals.** A day-by-day chart of new users, new conversations and new messages, over
|
|
the last week, month, quarter or year — each on its own scale, because a shared axis flattens a
|
|
handful of new users next to a few thousand messages into a line that says nothing.
|
|
|
|
**Who is actually using it.** A sortable, searchable table of every user, ranked by conversations
|
|
owned and messages sent, alongside when they last did anything and when they last signed in — the list
|
|
an administrator reaches for when a licence renewal asks whether a seat is worth keeping.
|
|
|
|
Two new database indexes ship with this release, applied the same way every schema change here is:
|
|
automatically, when the api starts. They only add — nothing is dropped, nothing becomes unique, so the
|
|
schema guard has nothing to refuse.
|
|
|
|
No new environment variable, no new permission: an existing platform administrator sees the page the
|
|
day this lands, anyone else does not see it in the menu, and a direct API call is refused.
|
|
|
|
## 2026-09-04 — An approval asks for your decision once, not twice `web 0.129.0`
|
|
|
|
**A request waiting on you carried Approve and Reject on the card itself, and pressing either one
|
|
opened a dialog that asked the same question over again.** Two consents for one decision — and the
|
|
second one explained consequences the first had already accepted. It read as a confirmation step. It
|
|
was really the only place the plan had ever been shown in full.
|
|
|
|
So the card decides nothing now. A pending request is a single row: its risk, its title, the first
|
|
two lines of what Jarvis intends to do, and how long it has been waiting, how many steps it runs and
|
|
how many machines it touches. Opening the row opens the request, and that is where the decision
|
|
lives — the whole plan, every step in order, the machines by name, what approving authorises and
|
|
what rejecting does, the note if you want to leave one, and the two buttons. The note is still
|
|
optional on both, and it is still recorded with the decision.
|
|
|
|
**Reading the plan is no longer gated on being able to act on it.** Somebody with view-only access
|
|
opens the same request and reads the same thing; they simply have nothing to press. That mattered
|
|
more than it sounds: the plan used to sit on the card, so folding the card down would have hidden it
|
|
from exactly the people who can only read.
|
|
|
|
Approvals already decided are unchanged — they still expand where they sit, with who decided and any
|
|
note they left. So is the approval Jarvis raises mid-conversation for a single command: that one is
|
|
one question about one thing, and it is still answered where it is asked.
|
|
|
|
No API change; take the web image on its own.
|
|
|
|
## 2026-09-01 — One byte in a command's output could destroy the turn that read it `api 0.106.0`
|
|
|
|
**If you have been seeing `Invalid prisma.message.update() invocation` in conversations, this is
|
|
it.** A single character in what a command printed made the assistant's turn impossible to store,
|
|
and the run died at the write — after the work had already been done on your machines. Reported this
|
|
morning, reproduced against a database within the hour.
|
|
|
|
Two characters do it, and neither is exotic:
|
|
|
|
- **A null byte.** PostgreSQL refuses it outright, in text and in JSON alike. It turns up in the
|
|
output of perfectly ordinary commands — reading a file with embedded nulls, `find -print0`, a
|
|
journal or LDAP dump, anything that touches something binary.
|
|
- **Half of an emoji.** This one was ours. Long output is shortened by cutting it, and the cut was
|
|
made without checking whether it landed in the middle of a character. An emoji in a log line, at
|
|
an unlucky offset, was enough to lose the turn that read it.
|
|
|
|
Neither carries any information, so both are now removed before anything is stored — as close to the
|
|
source as possible, so what the assistant reads, what you see and what is written down are the same
|
|
text. Tabs, carriage returns, ANSI colour and whole emoji are untouched; a character that was cut in
|
|
half shows as `�`, which is the truth about it.
|
|
|
|
**And a failed run no longer leaves a tool card spinning for ever.** The same report showed one that
|
|
had been turning for eighty-five minutes, above a turn that had already said it failed: closing a
|
|
run wrote its text but never its tool calls, so a call it never returned to kept the status it was
|
|
dispatched with. Those calls are now closed as cancelled, saying plainly that the command may have
|
|
reached the machine and its outcome is unknown — except one still waiting for an approval, which is
|
|
told it never ran, because it never did. That matters beyond the screen: the assistant reads those
|
|
rows back on the next turn, and "still running" makes it wait for an answer that cannot come.
|
|
|
|
No schema change, no console change. Take the api image.
|
|
|
|
## 2026-09-01 — Jarvis administers Kubernetes clusters `api 0.105.0` `web 0.128.0`
|
|
|
|
**Register a cluster the way you register a server, and ask for the work in words.** A Kubernetes
|
|
cluster is now a kind of asset, under **Virtualization & containers**, reached either by pasting a
|
|
kubeconfig into the vault or by pointing Jarvis at the API server with a ServiceAccount token. Any
|
|
conformant cluster: k3s, RKE2, kubeadm, EKS, AKS, GKE.
|
|
|
|
**What it can read.** Workloads (pods, deployments, statefulsets, daemonsets, replicasets, jobs,
|
|
cronjobs), nodes, namespaces, networking, storage, events, pod logs — including the previous
|
|
container's log, which on a CrashLoopBackOff is the only copy holding the error — live CPU and
|
|
memory where metrics-server is installed, and the Helm releases with their revision and status. The
|
|
output is shaped like `kubectl`, because that is what everyone reading the transcript already knows.
|
|
|
|
**What it can do.** Scale, restart a rollout, delete a pod so its controller replaces it, cordon and
|
|
uncordon a node, drain one, suspend and resume a CronJob. Draining uses the eviction API, so a
|
|
PodDisruptionBudget refuses an eviction that would take a service below its floor instead of the
|
|
drain quietly ignoring it.
|
|
|
|
**And, for what no fixed list covers:** read any API path (which is how custom resources —
|
|
cert-manager, Argo CD, CloudNativePG — are reached), apply a manifest with server-side apply, and
|
|
run a command inside a container. Those three are marked as tools whose operation the assistant
|
|
composes itself, so an access grant has to admit them deliberately rather than inheriting them from
|
|
a risk level.
|
|
|
|
**Risk is judged per call, not per tool.** Scaling is an ordinary change; scaling to zero takes the
|
|
service down and asks first. An apply is an ordinary change; an apply carrying RBAC, an admission
|
|
webhook, a CRD or anything at all into `kube-system` asks first, and says which of those it found.
|
|
Deleting anything always asks. Applying a namespace with the deployment that goes in it does not —
|
|
an approval on every deploy is an approval nobody reads.
|
|
|
|
**Two things it will not do.** No tool reads a Secret: the resource lists exclude them, and the Helm
|
|
tool reads the release index rather than decoding a release, which would carry the values a chart was
|
|
installed with. And a kubeconfig whose credential comes from running a program (`exec` /
|
|
`auth-provider` — the EKS and GKE default) is refused by name, with the ServiceAccount route named as
|
|
the answer, rather than failing later as an authentication error.
|
|
|
|
**Testing a connection records what the cluster is** on the asset: version, distribution, node count,
|
|
total capacity and node addresses.
|
|
|
|
Take both images. The schema gains one enum value and the migration is uneventful.
|
|
|
|
## 2026-08-31 — The Tasks panel now looks like the rest of the workspace `web 0.127.0`
|
|
|
|
**And its actions menu is reachable with a mouse again**, which is the part that was not merely
|
|
cosmetic. Pause, Run now and Cancel are revealed by hovering the card — and the card was missing the
|
|
one class that makes that work, so on any device with a pointer the menu was invisible and those
|
|
three actions were keyboard-only. Nobody would have found them.
|
|
|
|
The rest is how it sits beside Documents and Approvals. It did not: a hairline in the wrong token,
|
|
brighter than every other border in the panel; no card background; and a two-column list of labels
|
|
nothing else in the workspace uses. Tasks now renders the same card as an approval — status, title,
|
|
when it next fires, then the instruction — grouped under the same section headings, from the same
|
|
component rather than a copy of it.
|
|
|
|
Two things are easier to see as a result. A wake-up the platform **switched off** after three
|
|
failures is bordered in red and sorted to the top of the inactive group, instead of sitting below
|
|
the finished ones; its symptom is silence, so it needs to be the card you notice. And the
|
|
instruction is no longer clipped to two lines — it is one or two sentences Jarvis wrote to itself,
|
|
and the clipped half was usually the half saying what "check it" means.
|
|
|
|
No API change; take the web image on its own.
|
|
|
|
## 2026-08-31 — A wake-up no longer arrives under your name `api 0.104.0` `web 0.126.0`
|
|
|
|
**When a scheduled wake-up fired, the transcript showed it as a message you had sent.** Your avatar,
|
|
your name, and twenty lines of preamble written for the model — for something you never typed, at
|
|
whatever hour the clock came round. Reported within an hour of the feature shipping, and it was the
|
|
whole of what a transcript is for: knowing who said what.
|
|
|
|
A wake-up is now its own kind of row. It sits in the middle of the thread rather than on either
|
|
side, under a header saying which wake-up fired and when it was due, with one thing in the body:
|
|
the instruction Jarvis left itself. The scaffolding that turn actually ran on is folded away behind
|
|
**Show the note sent to Jarvis** — still there when a wake-up behaves oddly and you want to see
|
|
exactly what it was told, and out of the way the rest of the time. Wake-ups never merge into the
|
|
message above them either, so the header cannot be lost to grouping.
|
|
|
|
The same is true on a share link. A guest sees the same card; what reaches them is the title, the
|
|
time and the instruction, and not the internal id of the wake-up.
|
|
|
|
Underneath, the row is still recorded as coming from the person who owns the wake-up, because that
|
|
is whose access the turn runs with and it is how a wake-up interrupted by a restart is picked up
|
|
again. What changed is that the console no longer reads that as authorship.
|
|
|
|
**Nothing to do on the way in.** The schema gains one nullable column and the migration is
|
|
uneventful. Notes already in your transcripts keep rendering the old way — the structure is written
|
|
when a wake-up fires, so they have none; anything that fires from now on gets the card.
|
|
|
|
## 2026-08-31 — A scheduling loop, fixed at the shape rather than at the wording `api 0.103.0`
|
|
|
|
**If Jarvis got stuck trying to schedule something, this is the fix.** Reported within the hour:
|
|
asked to send a message a minute later, it tried four times, each time announcing it would correct
|
|
itself and each time sending the same thing. No wake-up was created, and every attempt showed as
|
|
"Succeeded" in the transcript.
|
|
|
|
Two mistakes of ours, and neither was in the scheduling itself.
|
|
|
|
**The tool asked for a choice it then punished.** It took an optional "repeat pattern" and an
|
|
optional "single time", and refused unless exactly one was given — a rule the assistant had to
|
|
enforce on itself, got wrong, and then could not recover from. It now takes **one** value for when
|
|
to wake, and the shape says which kind it is: a timestamp wakes it once, a five-field pattern
|
|
repeats. There is no longer a combination to get wrong. The value it kept sending was, as best we
|
|
can tell, a fragment of an example in our own field description — so the documentation was feeding
|
|
the mistake the tool was refusing.
|
|
|
|
**A refused call reported success.** The transcript showed four green rows for four calls that did
|
|
nothing, which is the opposite of what a transcript is for. A wake-up that cannot be created now
|
|
fails and says so on its row.
|
|
|
|
Nothing about existing wake-ups changes and there is no schema change. Take the api image; the
|
|
console is unaffected.
|
|
|
|
## 2026-08-31 — Jarvis can wake itself up, knows what day it is, and can read the web `api 0.102.0` `web 0.125.0` `browser 0.1.0`
|
|
|
|
**The schema step is uneventful for almost everyone** — unlike `api 0.101.0`. Read the upgrade
|
|
note below only if you created a scheduled task through the Settings screen this afternoon.
|
|
|
|
### Taking this build: what the schema step does
|
|
|
|
Reshaping scheduled tasks around the conversation drops the columns the organization-wide version
|
|
needed. **On almost every instance this passes without stopping**, and that is not luck: the guard
|
|
refuses a column drop when the table has rows in it, and `Schedule` is empty on every instance that
|
|
upgraded from `0.100.0` or earlier — the table only existed from `0.101.0`, published a few hours
|
|
before this. Watched on our own instance: dropped six columns and an enum, no warning, no
|
|
intervention.
|
|
|
|
**Read the rest of this only if that table is not empty**, which means you created a scheduled task
|
|
through the Settings screen that this release removes:
|
|
|
|
```sql
|
|
SELECT count(*) FROM "Schedule";
|
|
```
|
|
|
|
If it returns anything but 0, the migration will refuse and print what it would destroy. Those rows
|
|
are wake-ups you made today; note down what they were, then let it through **once** and put the
|
|
guard back:
|
|
|
|
- **compose** — set `SCHEMA_ACCEPT_DATA_LOSS=true` in `.env`, `docker compose up -d`, wait for the
|
|
api, then remove the line. Leaving it set would let a genuinely destructive change through
|
|
unannounced on some future release.
|
|
- **Kubernetes** — set the same variable on the `jarvis-migrate` Job, run it to completion, then take
|
|
it off. The api pods keep serving the old build while the Job fails, so there is no outage.
|
|
|
|
Then recreate them by asking Jarvis inside the conversation you want them to run in — which is where
|
|
they now live.
|
|
|
|
### Scheduled tasks are now part of a conversation
|
|
|
|
`api 0.101.0` shipped these a few hours ago as an organization-wide screen, and that was the wrong
|
|
shape. **The "Scheduled work" section and the `schedule:view` / `schedule:manage` permissions are
|
|
gone**; if you had started using them, nothing is silently broken — but read on, because what
|
|
replaced them is not in the same place.
|
|
|
|
The thing people actually want this for needs the conversation. Two cases, and both:
|
|
|
|
- **Waiting.** A rebuild is running, a DNS change is propagating, a maintenance window opens at
|
|
03:00. Jarvis leaves itself a note and picks the work up later with everything it had already
|
|
established still in front of it.
|
|
- **A standing check.** "Look at that file every day at noon." Every occurrence lands in the same
|
|
thread, so the seventh day can say "unchanged since Tuesday" instead of reporting into a vacuum.
|
|
|
|
The old version opened a _fresh_ conversation for each occurrence — which meant "carry on where you
|
|
left off" was impossible, because the woken assistant arrived knowing nothing. Now a wake-up belongs
|
|
to one conversation, wakes back into it, and appears under a new **Tasks** tab in that
|
|
conversation's workspace, beside the documents and the approvals. You pause, run and cancel from
|
|
there; you create one by asking ("check this again tomorrow morning"), because the instruction is
|
|
written with the conversation around it.
|
|
|
|
**It runs as whoever scheduled it, checked when it fires rather than when it was written.** If
|
|
somebody's access to a machine is revoked, the work they left behind stops reaching it the same day
|
|
and says so on its row. Three failures in a row switch a wake-up off, so a broken one becomes a
|
|
thing you find rather than a notification you learn to ignore.
|
|
|
|
Wake-ups inherit the conversation's autonomy and its asset scope; they cannot raise either.
|
|
|
|
### Jarvis knows what the date is
|
|
|
|
It did not before, and it did not know that it did not. Asked what day it is, a language model
|
|
answers from its training data — confidently, and usually months out. Everything an administrator
|
|
asks is dated: "the logs from the last week", "has this certificate expired", "wake me tomorrow at
|
|
noon". Each of those was a subtraction from a number nothing was giving it.
|
|
|
|
The current time is now stated at the start of every turn, and there is a `current_time` tool for
|
|
reading the clock during a long run and for converting into a customer's time zone. Nothing to
|
|
configure. If you have ever been given a date that was quietly wrong, this is why.
|
|
|
|
### Jarvis can read the web — if you turn it on
|
|
|
|
Two new abilities: search the web, and open a page and read it. A real browser, so pages that build
|
|
themselves with JavaScript work — vendor advisories, status pages, release notes, consoles.
|
|
|
|
It also fixes the same class of problem as the clock: a model's knowledge has a cut-off. Versions,
|
|
end-of-life dates, advisories and prices are exactly what it will otherwise answer from memory and
|
|
get wrong, so it is now told to look things up instead.
|
|
|
|
**It is off by default, and turning it on is a deliberate act.** An assistant that can read the
|
|
internet is a new kind of input into a system that administers your customers. To enable it:
|
|
|
|
- **compose** — `COMPOSE_PROFILES=browser docker compose up -d`, and set `BROWSER_URL=http://browser:3000`.
|
|
|
|
The browser image is on the `dev` channel and pinned by version in the compose file, not on
|
|
`stable` like the other three. That is deliberate and not an oversight: promotion to `stable` means
|
|
"this has run on a canary", and no canary deploys a browser — it is opt-in there too. So it is
|
|
pinned to bytes that exist rather than to a channel that does not.
|
|
|
|
- **Kubernetes** — uncomment `09-browser.yaml` in `kustomization.yaml` and `BROWSER_URL` in
|
|
`02-config.yaml`.
|
|
|
|
With it off, the two tools report themselves unavailable and nothing else changes.
|
|
|
|
**Chromium runs in its own container, never in the api.** The api holds your vault key and talks to
|
|
your database; a browser's whole job is to run code written by strangers. Keeping them apart is what
|
|
stops a web page being used to read your database, your cache or your cloud provider's metadata
|
|
endpoint. The browser container holds no secret, no volume and no database credentials, and the
|
|
Kubernetes manifest ships a NetworkPolicy that denies it every private address range. **That policy
|
|
needs a CNI that enforces NetworkPolicy** — Calico, Cilium and most managed clusters do; plain
|
|
flannel does not, in which case the in-process address check is the only control and it cannot
|
|
survive a redirect.
|
|
|
|
**What comes back is treated as untrusted.** Page text is delimited and labelled as data rather than
|
|
instruction, because a page can be written specifically to be read by an assistant with access to
|
|
your systems. The tools only read: no clicking, no forms, no signing in.
|
|
|
|
## 2026-08-31 — Jarvis works while you are asleep, and can finally administer Exchange `api 0.101.0` `web 0.124.0`
|
|
|
|
Two capabilities, and both are about the half of managed services that does not happen in a
|
|
conversation: the work that is on a calendar, and the half of Microsoft 365 that Graph does not
|
|
reach.
|
|
|
|
**The schema step stops once on the way in — see the upgrade note directly below.**
|
|
|
|
### Taking this build: the schema step stops once, on purpose
|
|
|
|
**Read this before you pull.** The migration refuses the first time and says so, and on compose that
|
|
leaves the API down until you act. It is not a bug and there is nothing wrong with your data — it is
|
|
the data-loss guard doing its job over a change that happens to be harmless.
|
|
|
|
You will see:
|
|
|
|
```
|
|
⚠️ There might be data loss when applying the changes:
|
|
• A unique constraint covering the columns [scheduleId,scheduledFor] on the table
|
|
Task will be added. If there are existing duplicate values, this will fail.
|
|
Error: Use the --accept-data-loss flag …
|
|
```
|
|
|
|
The guard cannot know that the constraint is harmless; it only knows a unique index _could_ fail on
|
|
existing duplicates. **The `Task` table has never had anything writing to it** — it was a placeholder
|
|
until this release gave it a producer — so on any instance upgrading from an earlier build it is
|
|
empty, and adding the index to an empty table cannot lose anything. Check it yourself if you would
|
|
rather not take our word for it:
|
|
|
|
```sql
|
|
SELECT count(*) FROM "Task"; -- expected: 0
|
|
```
|
|
|
|
Then let it through **once**, and put it back afterwards:
|
|
|
|
- **compose** — set `SCHEMA_ACCEPT_DATA_LOSS=true` in `.env`, `docker compose up -d`, wait for the
|
|
api to come up, then remove the line again. Leaving it set would let a genuinely destructive
|
|
schema change through unannounced on some future release.
|
|
- **Kubernetes** — set the same variable on the `jarvis-migrate` Job, run it to completion, then
|
|
take it back off. The API pods keep serving the old build while the Job fails, so there is no
|
|
outage — the rollout simply waits.
|
|
|
|
If that query returns anything other than 0, stop and ask before forcing it.
|
|
|
|
### Scheduled work
|
|
|
|
Everything Jarvis did until now happened inside the turn that asked for it. That is right for an
|
|
investigation and wrong for the licence reconciliation on the first of the month, the mailbox audit
|
|
every Monday, or the service restart at 03:00 on Sunday because that is when nobody is using it.
|
|
|
|
**A new section, "Scheduled work".** Write an instruction, choose when it runs — once, or on a
|
|
repeating pattern — and it runs on its own, in a conversation you can read afterwards. You can also
|
|
just ask: "check the backups every morning at seven" now works in a conversation, and what Jarvis
|
|
writes down is an ordinary row on the same screen.
|
|
|
|
**Times are yours, not the server's.** A repeating schedule is stored with the time zone you wrote
|
|
it in, so 02:00 stays 02:00 when the clocks change. The form shows you the next three runs it will
|
|
actually make, because a cron expression is otherwise something you find out about afterwards.
|
|
|
|
**It runs as the person who scheduled it, and that is checked when it fires — not when it was
|
|
written.** If somebody's access to a machine is revoked, the work they left behind stops reaching
|
|
that machine the same day and says so on its row. Nothing about their rights is stored on the
|
|
schedule, so there is no standing permission to forget about.
|
|
|
|
**Nobody is at the keyboard, and Jarvis behaves accordingly.** An occurrence that would stop for
|
|
your approval has nobody to ask: a scheduled _instruction_ waits in the transcript as usual, and a
|
|
scheduled _single operation_ is refused with a sentence telling you to raise the conversation's
|
|
autonomy or narrow the operation. It will not sit there for an hour and then fail anyway.
|
|
|
|
**Failures are bounded so they stay readable.** One failure changes nothing — a tenant throttled on
|
|
Tuesday is usually fine on Wednesday. Three in a row switch the schedule off and mark it, because
|
|
the thirtieth identical failure is one nobody reads, and a switched-off schedule is what makes
|
|
somebody go and fix the credential.
|
|
|
|
**Two new permissions**, `schedule:view` and `schedule:manage`. Viewers can see what the
|
|
organization has scheduled; operators and above can write and cancel. Neither reaches a machine on
|
|
its own — what an occurrence may actually do is still the asset grant of whoever owns it.
|
|
|
|
**Worth knowing before you use it:** scheduled work is visible to everyone in the organization who
|
|
can see the section, including the conversation each occurrence writes into. If you pin a schedule
|
|
to an existing private conversation, that conversation's name becomes visible alongside it. Leave
|
|
the schedule to open its own conversation each time if that matters to you.
|
|
|
|
**Nothing to configure.** The schema change is applied by the usual migrate step; there is no new
|
|
environment variable and no new service.
|
|
|
|
### Exchange Online
|
|
|
|
Jarvis reaches Microsoft 365 through Microsoft Graph, and Graph does not reach Exchange. Mail flow
|
|
and transport rules, connectors, accepted domains, journaling, retention and litigation hold,
|
|
mailbox and calendar permissions, shared and resource mailboxes, anti-spam and anti-phishing policy,
|
|
address lists, message trace — none of it has a Graph endpoint. Until this build, Jarvis could
|
|
manage users and licences in a tenant and could not touch any of that.
|
|
|
|
**It now runs the Exchange Online PowerShell module**, which is the only administrative surface
|
|
Microsoft supports for those areas. It uses the same app registration you already configured for the
|
|
Microsoft 365 connection — including one that authenticates with a client _secret_, which the
|
|
documented Microsoft recipe does not allow for app-only Exchange.
|
|
|
|
**You have to grant it two things, and only the first looks like a normal permission.** On the app
|
|
registration, add `Exchange.ManageAsApp` as an **application** permission and grant admin consent.
|
|
Then assign the application's service principal a directory role Exchange honours — **Exchange
|
|
Administrator** to make changes, **Global Reader** to read only. Graph consent alone authenticates
|
|
perfectly and is then refused by every cmdlet, which is the usual way this ends up looking broken;
|
|
`m365_check_permissions` cannot see either of these, because neither is a Graph permission. **Test
|
|
connection** on the tenant now tells you whether the PowerShell side is available at all.
|
|
|
|
**The two new operations are treated as unrestricted**, like the raw Graph request and the shell
|
|
commands: a person needs the "unrestricted operations" switch on their asset grant to use them, and
|
|
that switch is off by default. This is deliberate and worth taking seriously — an Exchange script is
|
|
as open-ended as a shell command, and the risk classification in front of it is a list of cmdlet
|
|
verbs that cannot be complete.
|
|
|
|
**The api image is about 210 MB larger** because it now carries PowerShell 7 and the module. If you
|
|
mirror images or run on constrained storage, this is the change to plan for. Nothing else about
|
|
running the stack changes, and every existing tool works exactly as before on an image without it —
|
|
only the two Exchange operations depend on it.
|
|
|
|
## 2026-08-31 — Nothing in the console changes, and that is the whole of it `web 0.123.0`
|
|
|
|
**This is the same console as `web 0.122.0`.** No behaviour changed, no bug was fixed, nothing in
|
|
the interface moved. The two images differ only in the version they report about themselves.
|
|
|
|
**It exists to exercise a rollout end to end**, against a cluster that is actually serving: a new
|
|
digest pinned, the pods replaced one at a time under a disruption budget, the previous ones still
|
|
answering until the new ones are ready. A rollout path nobody has run is a rollout path nobody
|
|
knows about, and the honest way to learn it is on a build where being wrong costs nothing.
|
|
|
|
**Nothing to do.** If you are on 0.122.0 there is no reason to take this, and if you take it there
|
|
is nothing to notice. The next entry describes a real change.
|
|
|
|
## 2026-08-29 — Keep several conversations on the go, and switch between them `api 0.100.0` `web 0.122.0`
|
|
|
|
Opening a conversation used to mean being _in_ it: to reach another you left it, found the one you
|
|
wanted in the list, and went in. That is a poor deal when Jarvis runs unattended for twenty minutes
|
|
against a real machine — the thing you most need to know is that a plan is blocked in a thread you
|
|
are not looking at.
|
|
|
|
**The console now keeps a working set.** The conversations you are actually in appear in the sidebar,
|
|
under the main navigation, alongside anything of yours that is running or waiting on a decision. It
|
|
costs no width — the transcript is exactly as wide as it was — and it shows nothing at all until you
|
|
have opened something. On a phone the same list is one tap away from the top bar and from the
|
|
navigation drawer.
|
|
|
|
**A blocked plan is decidable without opening it.** The row carries the plan's own title, its
|
|
summary, its risk, the machines it names and how long it has been waiting. Choosing it lands you on
|
|
the transcript with the approval already open, rather than on a transcript with the decision hidden
|
|
behind a panel.
|
|
|
|
**Switching is two keystrokes.** `Ctrl+K` (or `Cmd+K`) now opens with your recent conversations
|
|
first and the previous one already selected, so `Ctrl+K` then `Enter` goes back to it. `Ctrl+\`
|
|
toggles between the last two. You can pin up to five conversations so they stay in place.
|
|
|
|
**Your browser tab is finally named after what is in it** — the conversation and the client, rather
|
|
than "Jarvis" on every tab. If you keep two tabs open on two clients, this is the change you will
|
|
notice first.
|
|
|
|
### Two things that were wrong, and are fixed here
|
|
|
|
**Activity was announced to everyone in the organization.** The "Jarvis is working" marks in the
|
|
conversation list were broadcast to every member of an organization, for every conversation in it —
|
|
including private conversations they take no part in and cannot open. Nothing in the interface ever
|
|
displayed them, but the identifiers, the live state and the _name of the colleague running the
|
|
thread_ were delivered to browsers that had no business receiving any of it. They now go only to the
|
|
people who take part in the conversation, and organization membership is re-checked at the moment of
|
|
sending rather than trusted to a stale row. **Take both images to close this**; the api is the half
|
|
that matters.
|
|
|
|
**A half-typed prompt could follow you into another conversation.** Leaving a conversation
|
|
mid-sentence did not discard the text — it carried it into whichever conversation you opened next,
|
|
in a box that looked empty. Where one operator works across several clients, that is a command
|
|
written for one machine sitting one keypress away from being sent to another. Drafts are now kept
|
|
per conversation, survive a reload, and are cleared when the message is sent.
|
|
|
|
Two smaller ones alongside them. **Deleting a conversation now takes anybody reading it off the
|
|
screen** and says why, instead of leaving them on a transcript whose every control has quietly
|
|
stopped working. And **signing out now clears what the console left on the machine** — the
|
|
notification history, which conversations had been read, and any unsent drafts. Your theme, language
|
|
and sidebar preference are deliberately kept: a preference is not a secret.
|
|
|
|
**Nothing to do.** Take both builds. No new settings, no schema change.
|
|
|
|
## 2026-08-29 — The conversation list shows what is running, wherever it is running `api 0.99.0`
|
|
|
|
**Only affects deployments running more than one copy of the api.**
|
|
|
|
A conversation could sit in the list with no activity mark while Jarvis was, at that moment, running
|
|
a tool in it — and opening that conversation showed the real state straight away. Both views were
|
|
honest about what they asked; only one of them was asking about the whole deployment.
|
|
|
|
The list is fed by a live stream of changes, so a browser that connects part-way through is first
|
|
sent a summary of what is already in progress. That summary was assembled from whichever replica the
|
|
browser happened to connect to, and it only knew about the runs it was executing itself. With three
|
|
replicas the list was right about a third of the time, and everybody's browser re-rolled that when a
|
|
proxy restart made them all reconnect at once.
|
|
|
|
The summary now comes from the database, which knows about every replica's runs and already tracks
|
|
which of them are still alive. A conversation being worked on elsewhere shows as thinking until its
|
|
next update arrives — a moment later — rather than showing as idle for as long as the work lasts.
|
|
|
|
A replica that is killed mid-run also stops appearing on its own, so nothing can leave a permanent
|
|
spinner in your list.
|
|
|
|
**Nothing to do.** Take the build.
|
|
|
|
## 2026-08-28 — A machine that moves between replicas lets go of the old one `api 0.98.0`
|
|
|
|
**Only affects deployments running more than one copy of the api.** Nothing here changes a single-
|
|
container stack.
|
|
|
|
**Reconnections are tidied up immediately instead of being waited out.** When a machine reconnects
|
|
to a different replica, the one it left still held its old connection until its own liveness check
|
|
noticed — up to thirty seconds in which two replicas each believed they could talk to the same
|
|
machine, and a command could be sent down the connection the machine had already abandoned. The
|
|
replica that takes over now tells the other one to let go, straight away and precisely: only the
|
|
connection that was actually replaced is closed.
|
|
|
|
**And you can now see what the presence machinery is doing**, at
|
|
`GET /api/admin/agents/presence`, for platform administrators. It reports five counters, added up
|
|
across every replica rather than only the one that answered your request:
|
|
|
|
- how often a machine's connection moved from one replica to another;
|
|
- how often a closing connection was found to have already been replaced, and correctly ignored;
|
|
- how often a command followed a machine that moved instead of failing;
|
|
- **how often a command could not be delivered at all** — this is the one worth alerting on, because
|
|
it is what someone experiences as "the agent is not connected";
|
|
- how often a stored status had to be corrected because it said a live machine was offline. This
|
|
should sit at zero; if it does not, please tell us.
|
|
|
|
It is JSON, every counter only ever goes up, and a replica that has stopped reporting is excluded
|
|
from the totals and counted separately — so you can point a scraper at it with a service account.
|
|
There is deliberately no unauthenticated `/metrics` endpoint: your replica count and machine totals
|
|
are facts about your estate, not about Jarvis.
|
|
|
|
**Nothing to do.** Take the build.
|
|
|
|
## 2026-08-28 — Machines no longer flicker offline when they reconnect `api 0.97.0`
|
|
|
|
**Only affects deployments running more than one copy of the api.** On Docker Compose one of these
|
|
could still happen — a machine reconnecting to the single container — and the fix applies there too,
|
|
but the wave of them does not.
|
|
|
|
**The one to read if you run several replicas.** When something in front of Jarvis closes a batch of
|
|
agent connections at once — a rollout of your ingress controller, a proxy being replaced, a load
|
|
balancer reloading — every affected machine reconnects within a second. That is normal and the
|
|
agents handle it. Jarvis did not: the reconnections raced the bookkeeping for the connections they
|
|
were replacing, and machines that were connected and working were recorded as offline. Calls to them
|
|
were refused with "the agent is not connected" while a terminal to the same machine was open.
|
|
|
|
Four things were wrong underneath, and all four are fixed:
|
|
|
|
- **A connection that had already been replaced still did the bookkeeping for its own ending** —
|
|
including writing "offline" about a machine whose new connection was already live.
|
|
- **Two connections from the same machine to the same replica could not be told apart**, so the old
|
|
one's cleanup deleted the new one's registration, and the old one's keep-alive timer kept a dead
|
|
registration alive.
|
|
- **Registration was published without waiting**, leaving a moment where a replica held a working
|
|
connection that none of the others could see.
|
|
- **A command sent to the replica that held the machine a moment ago gave up** instead of asking
|
|
again where it had moved. It now retries once, so a reconnection costs a pause rather than an
|
|
error.
|
|
|
|
**What you should see afterwards.** Reconnections still happen when proxies restart — that part is
|
|
not something Jarvis can prevent — but they stop producing false "offline" states, and commands in
|
|
flight during one survive it.
|
|
|
|
**Nothing to do.** Take the build.
|
|
|
|
## 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.
|