Sync the self-hosting stack

This commit is contained in:
2026-10-01 08:14:29 +02:00
parent ef1b961d2d
commit b8cf169331
2 changed files with 371 additions and 11 deletions
+299
View File
@@ -9,6 +9,305 @@ one of them changed. An entry says which artefact and which version, so `web 0.1
Every version published to a channel appears here. Nothing is written from memory afterwards: a build Every version published to a channel appears here. Nothing is written from memory afterwards: a build
cannot be published without its entry. cannot be published without its entry.
## 2026-10-01 — Remote desktop: watch a Windows machine, and take the mouse `api 0.117.0` `web 0.142.0` `agent 0.28.0`
**Remote desktop opens.** **Agents** → a Windows machine → **Watch the screen**, and you see its
screen, live, for as long as you keep the tab open. Three things about it are worth reading before
you update rather than after: you can also **type and move the mouse**, not only look; the two
switches that allow it are turned on for your owners and administrators **by this update itself**,
once, and **no audit entry is written when that happens**; and the way back out is a switch in the
product rather than a rollback, because an agent cannot be un-updated.
**You can watch, and you can take the mouse and the keyboard.** Sight and control are two separate
grants and control is the second one — the viewer says "Watching only" and sends nothing at all for
as long as that is what you hold. With control, the keyboard and the mouse go to the far machine as
they would from a keyboard plugged into it: modifiers, the numeric keypad, AltGr, the wheel, and a
right-click. Typing into somebody else's Windows session is a different piece of Windows from reading
pixels out of it, so it refuses in places reading does not — see the black screens below.
**`Ctrl`+`Alt`+`Del` works from a session you hold control of, and only where the machine's own policy
allows it.** It is the one keystroke user-mode software cannot forge, which is the whole point of it:
the person at the
keyboard has to be able to trust that the sign-in screen in front of them is real. A machine permits
it only with bit 0 of `SoftwareSASGeneration` set, which is a policy **you** set on **your** machines
and that this release deliberately does not set for you. Where it is not set, the control is not
offered — and it is not offered on a watch-only session either, so somebody holding "Watching only"
finds it missing on a machine whose policy is right, with nothing on screen to say why. Write `3`
rather than `1`: both set bit 0, and `1` also widens the sequence to every service on the box while
closing the accessibility path Windows leaves open by default, which is how an on-screen keyboard or
another vendor's remote-control tool stops working. Do not set the policy on a machine whose agent
predates this release: the policy never travels from an older agent, so the edit buys nothing and
leaves the machine looser than it was. And what the sequence summons is a screen the viewer
deliberately will not show you — nor is it a way in to a machine already locked in front of you, where
the keystroke is refused along with every other one. It is for the machine somebody is signed in to: a
wedged shell that needs Task Manager, a password to change, a session to sign out.
`docs/operations.md` has the value, what it means, and how to read it off a machine; the
agent version to require is this one, `agent 0.28.0`.
**Nothing is captured unless a notice naming you is standing on the screen.** Your organisation's
policy decides how, at
**Settings → Remote desktop**, and every organisation starts on the banner. On **Show a banner**,
nothing at all is captured until a notice naming you exists, is mapped and holds the topmost window
style — which is as close to "visible" as Windows lets anything check: if it cannot be drawn the
session never starts, and if that window is destroyed or loses its topmost standing the session ends.
What is left open is a notice still standing that a later topmost window covers; we do not sample the
screen back to find out, and that is the one hole in this story we know of. On **Ask first**, nothing
is captured until the person accepts, and a refusal or a minute of silence ends it before it began —
recorded as their refusal, or as a question nobody answered, deliberately two different endings and
neither of them a failure. There is no third mode, no
override, and nothing an operator can do from their side to skip either one. The one deliberate
exception is a machine with nobody signed in: there is nobody to ask, so the session starts with the
notice shown and no question. **If somebody then signs in, the notice is taken down and raised again on
their own screen**, and the picture stops until it is standing there — so they are told, though nobody
goes back and asks them, which is the honest description of that one case. A machine nobody can log
into is exactly the machine an operator needs this for.
**Being told is not the same as being able to stop it, and the difference is worth stating.** The
notice is click-through on purpose — no click, no menu, no keyboard — so that it can never sit
between somebody and their work, and cannot be closed by accident or by a script. The cost is that
the person at the keyboard cannot dismiss the viewer from the notice itself. Their ways out are
elsewhere: refusing the prompt under the ask policy, before anything is captured; signing out, which
ends the session with it; or an administrator turning the feature off for the instance. What the
banner guarantees is that watching is never secret. Under the banner policy it is not asking for
consent, and it does not pretend to.
**A sentence of your own is added to the notice, never put in its place.** Whatever you write — a
policy reference, the number of your service desk — appears after the standard wording, so nothing
anybody can set here makes a session a quiet one. It is a sentence and not a page: 280 characters, and
longer is refused rather than trimmed. It is added to the banner only, and deliberately not to the
**Ask first** prompt, which stays the short question it has to be — so a number you add so that people
can check with you before accepting is not on the window where they accept.
**The language is the one the person at the machine reads, which is often not the one you read.** They
may have no Jarvis account at all: it is a customer's employee looking at their own screen. The server
composes the finished sentence in the language you pick and hands the machine that text, so the notice
is never assembled on the far end out of pieces, in a language nobody on your team could have checked.
English, French and German. The settings page previews it in the language you chose rather than in
yours — a preview in your own language would reassure you about a sentence nobody is ever shown. The
two buttons on the **Ask first** prompt are the one exception: a label that arrives empty or too long
for the button is drawn with the compiled-in English word instead, because a button with its text cut
off is worse than a button in the wrong language on the window where somebody decides.
**Changing any of that takes the right to change the organisation itself** — owners and
administrators, the same people who can rename it.
**Who may watch, and who may type: two new switches on an access grant**, at **Settings → Access**,
beside the one that gives a shell: **Watch the screen**, and under it **Move the mouse and type**.
They work exactly like the shell switch — per machine, per folder or for the whole organisation, most
specific rule wins — and they are independent of it in both directions. A machine room full of
headless servers is shells with no screen worth seeing; helping a colleague through their own desktop
is a screen with no business holding root. Neither implies the other, and neither implies anything
about what the assistant may do. **Control cannot be given without sight:** the second switch only
turns on when the first is on, and turning sight off turns control off with it. **Who may hold it:**
operators and above, the same as the shell, with a platform super-admin outside that answer as they are
everywhere else. A viewer's grant can be written with either switch on and reaches nothing — the
refusal is at the session, not at the row. **Who may hand it out** is a narrower question with the same
answer it has always had: writing grants belongs to owners and administrators, so an operator handed
sight of a client's machines passes nothing on to anybody. Where the ceiling bites is an owner or
administrator whose own row has been narrowed — holding sight but not control, they can write a
colleague a watch-only row and cannot write one that types; holding both, which is what the default row
for both roles carries, they pass on both.
**Owners and administrators are given both, once, by the schema sync — and the audit trail does not
record it.** Without it, turning the feature on at all would mean writing the first row from the
platform super-admin account, because nobody hands out access they do not themselves hold and no
ordinary grant would carry it. Only a row that **already** carries a shell on the whole organisation,
unbounded and not narrowed by a risk ceiling of its own, is widened: somebody an administrator has
decided not to trust with a terminal — or whose terminal was deliberately narrowed — is not handed
screens by an update, and needs a grant written by hand. **One exception to that, and it is the row to
check first:** an organisation where **nobody at all** currently holds a shell has the shell handed back
to its owners and administrators by a separate, older pass that runs moments earlier — and the screen
pass then widens those same rows. On a one-owner instance "nobody holds it" is simply "I took it off
myself", so if that is you, you get the shell back, plus the screen and the mouse, in one start. The
widening is part of the schema sync, which on compose is the api
container's own migrate step and on Kubernetes is the `jarvis-migrate` Job, so on Kubernetes nobody
is widened until that Job has run. It happens once per installation and is then stamped, which is
the whole of "once": if the stamp fails, or the process is killed between the first widening and it,
the pass stays armed and the next start hands both switches back to every owner and administrator row
that has since been revoked. The migrate log says so and names the column to set by hand — except in
the killed case, where it says nothing at all; rows that fail individually are skipped and counted in
the same log. Nobody else is touched. The missing entry is the part to know about: every _later_
change made on the grants screen is audited, but this widening is done by the schema sync rather than
by a person and writes no entry naming who now holds what — and neither does the default row a new
owner or administrator is seeded with when they are added or accept an invitation, which carries both
switches too. If your rule is that screen access is granted deliberately, read the grants of your
owners and administrators after updating, and again after adding one, rather than trusting the trail
to tell you.
**The audit trail now records the shell and the screen when a grant changes**, with their previous
values, which it did not before. "Who gave this person a shell on that client, and when" could not be
answered from the trail — the entry said that a grant had changed and which connectors it named,
while the switch that decides whether a human gets administrative control of the machine was
invisible. It is a gap that predates screen access; it is closed here rather than left for a third
switch to inherit.
**Windows security screens go black, deliberately.** A sign-in prompt, a `Ctrl`+`Alt`+`Del`, an
administrator-approval dialog, a lock screen: Windows puts each of those on a desktop of its own, and
the agent captures nothing but the one desktop its notice is standing on — so one that comes up during
a session arrives as a blank canvas. The refusal is ours rather than something the operating system
does for us, which is worth knowing if you are deciding how much of this to take on trust. **A session
you open while one of them is already standing is the exception, and it is deliberate:** the notice is
raised on whatever desktop is receiving input at that moment, so on a machine sitting at its sign-in
screen — the case this feature most exists for — the notice goes on the credential desktop and the
picture is of that. The rule being obeyed is the one rule in all of this, that the notice stands on the
desktop being captured and never beside it, rather than a list of screens by name. What the agent
refuses is to _move_ a notice onto a security desktop once a session is running, where the alternative
is a blank canvas rather than a secret one. The viewer tells you a Windows
security screen is up — a sign-in prompt, or a
request for administrator approval — without saying which, because what the machine can report is that
the desktop changed and not what is on it; a screen saver, or a desktop some other vendor's tool
created, reports the same thing under the same words. The picture resumes on its own when the screen
goes away, rather than showing you a frozen frame of the desktop underneath, which is the thing a
remote viewer must never do. Keystrokes do not reach those screens either. `Ctrl`+`Alt`+`Del` goes by
a route of its own for a separate reason: three injected keystrokes arrive on an ordinary desktop as
three ordinary keys and the sign-in process ignores them, so the sequence has to be asked for through
a dedicated Windows call rather than typed — and that call reports nothing back, so the only
confirmation anybody gets is the picture going black.
**Nothing is recorded, and what is kept is not nothing.** No video, no screenshot, no transcript, and
no setting anywhere that turns one on. What is kept is the session record: who connected — or that it
was a platform role rather than a grant — which machine, which consent policy applied, **whether
control was allowed**, how much data went each way, **how many input events were accepted and how
many were dropped**, when it started, when it ended and why; and then, once per screen watched,
which Windows session and the account signed in to it, which monitor, whether consent was agreed and
when, and whether it was accepted without anybody clicking — which is every session under the banner
policy, and an ask-policy session with nobody signed in. A session that moves to another screen keeps
one of those rows per stretch, each with its own consent, and that is the part a staff disclosure most
needs: whose desktop, for which stretch. The size of the picture is deliberately not stored at all.
Unlike a terminal transcript, that record has no retention setting and nothing sweeps it — but it is
keyed on the machine: deleting an asset takes its session rows with it, and deleting an account blanks
who connected. What outlives both is the platform audit trail, which keeps the open, the consent and
the close with no retention at all. Write your staff disclosure from that list rather than from the
first sentence.
**Windows only, and a machine that cannot do it says so instead of failing.** Your Linux and macOS
agents announce no screen and are never offered one — they go on doing everything they did, and the
server refuses a screen on any machine whose agent did not claim to have one. The row for a Mac or a
Linux box carries the action dimmed and **named** "Watch the screen — Windows only", and _named_ is
the exact word rather than a hedge: on the **Agents** page that entry is promoted into an icon-only
button, so a screen reader reads the sentence out, a touch device and the machine's own row on
**Assets** print it as words in the menu, and a mouse-driven browser shows a greyed monitor glyph with
the OS column beside it as the visible answer. The tooltip does not open, because a disabled control
takes no pointer. **And that control being live is a statement about the machine, not a promise about
your grant.** It is drawn for anybody whose organisation role may watch screens at all: a Viewer never
sees it, while an operator who holds the role but no **Watch the screen** grant sees it enabled on
every connected Windows row and is refused in words when they use it. The grant is checked when the
session opens, not when the menu is drawn.
**Sessions end by themselves.** Four hours for any one session — and the card announcing that ending
offers to open another, so the ceiling bounds a forgotten tab rather than how long somebody can be
watched. A session also ends if the viewer stops keeping up with the picture for twenty seconds, which
on a bad connection is a thing you will meet; it comes back with the same button. Switching to another
browser tab does not end a session: the picture pauses, the machine stops sending while the tab is
hidden, and both resume when you come back — a window merely covered by another window is not hidden
and goes on costing what it cost, which is a trade of liveness for bytes made on purpose. A tab left
paused for fifteen minutes
is ended, give or take the fifteen seconds it takes the next re-authorisation sweep to notice, on the
grounds that a viewer which has said it is not looking and has gone on not looking for that long
should not hold a helper process and a banner on somebody's screen until the four-hour ceiling. A
session you are actually watching is not idle no matter how long you watch it without touching
anything, which is the right answer for a screen and the wrong one for a shell.
**Two sessions at once on any one machine, and sixteen on any one api pod.** A third attempt on the
same machine is refused and says how many people are already watching it — that count is read for the
sentence and never for the gate, so when it cannot be read the refusal still stands and says only that
the machine is showing its screen to as many people as it allows. Neither wording names the machine.
Nothing already running is ended to make room, and two tabs
belonging to one operator fill the two seats exactly as two operators would. The per-machine number is
compiled into the agent and is the only thing that enforces it, so it cannot be changed from the
instance and moves only with an agent release. The per-pod number is the one a single-replica instance
meets first, and its refusal says to try again in a moment.
**Separately, a terminal session's flow-control window is now clamped to what the server actually
sent.** A terminal holds output back until the browser confirms it has caught up, and that is what
makes a runaway process on the far machine slow down instead of flooding the session. A browser could
confirm more than it had been sent, which switched that brake off for the rest of the session on a
machine reached through its Jarvis agent — no privilege beyond a terminal the person was already
entitled to open, and nothing to see afterwards but load. Sessions reached over SSH were never
affected: that transport re-applies its own brake every 256 KiB, and has in every released version. The confirmation is now capped at what was sent and not yet confirmed, so it can never be worth
more than the output it answers. A malformed confirmation is ignored rather than jamming the session.
Nothing changes for an ordinary session.
**Self-hosters: there is one switch that turns all of it off, and it is the way back.** Settings →
Platform → **Agents** → the **Remote desktop** card, where the switch reads "Screens can be watched".
The tab used to be called "Agent updates" and is called **Agents** now, renamed because an operator
looking for a kill switch had no reason to open it. Do not confuse it with the page also called
**Remote desktop** at Settings → Remote desktop: that one is per organisation, holds the consent
policy, and cannot stop anything. Turning the instance switch off refuses every new session and ends
the ones already
running within fifteen seconds, on every machine at once, without touching terminals, agent updates or
anything else the agents do. It exists because the api and the web images roll back by digest and an
agent release does not: once `0.28.0` is out on your fleet it is out, so the instance needs a way to
say no that does not depend on recalling it. Know where it is before you need it — and know that the
page is platform-super-admin only, so an organisation's own administrators cannot reach the switch and
will have to ask whoever holds that account.
**Self-hosters: the agents update themselves on the default policy — and on `MANUAL` or `DISABLED`
they do not, so there you push the update yourself before there is anything to watch.** Screen capture
and input live in `agent 0.28.0`. An older agent connects and works exactly as before; the action on
its machine is dimmed and named "Watch the screen — agent too old", read the way described above,
rather than opening a session that cannot start. An agent that is merely offline is named that
instead — a third distinct label, in the same place and read the same way. Publish the api first,
then the web, then the agent — on the bundled compose overlay those are not three steps, since it
couples the agent release to the api's start, and the new release lands in its volume first. That is
harmless, and not a sign you have done it in the wrong order.
**Self-hosters: check your reverse proxy before you update.** The api now listens on a **third**
WebSocket path on the same `JARVIS_PORT` — `/api/agents/ws/desktop`, beside `/api/agents/ws` and the
browser's socket. A proxy that forwards `/api/agents/ws` by **exact path** rather than by prefix will
pass the first two and fail this one: terminals keep working, agents stay green, and every attempt to
watch a screen fails at once, with the agent naming the URL it could not reach — "the picture
connection to wss://…/api/agents/ws/desktop was refused (HTTP 404)", in the agent's log and on the
operator's error card, which is the diagnosis handed to you. Match by prefix. Both
configurations shipped here already do — the bundled nginx matches `^~ /api/agents/ws`, and the
Kubernetes ingress uses `pathType: Prefix` — so a stock deployment needs no change. It is a
hand-written proxy in front of one of those that is worth a look.
**Self-hosters: the schema.** `pg_dump` first, as for any api minor bump. It gains two tables,
`DesktopSession` and `DesktopSessionTarget`, two enum types, three columns on an organisation for the
consent policy, two columns on an access grant for the switches, and two on the instance's own
settings — the kill switch further down, and a timestamp recording that the widening has run.
Everything is defaulted and it only adds, so the schema guard has nothing to refuse. No new
environment variable, and nothing to run by hand. **On Kubernetes, the schema is applied by the
`jarvis-migrate` Job and not by the api pods** — an arrangement that landed in the _previous_ release
rather than in this one, so if you maintain your manifests by hand, check you actually have
`03-migrate-job.yaml` and `JARVIS_SKIP_MIGRATIONS=true` on the Deployment before you rely on it. **That
Job has to finish before the api Deployment, and there is no safe order the other way round.** An api
image that reaches a database the Job has not touched may not get as far as listening: its boot seeds
the instance settings row, that write now returns the two new columns with it, and on an installation
whose row is still missing a public URL, a proxy-hop count or the vault-key timestamp — the last of
which is every Kubernetes install made before api 0.114.1 — the column is not there, the boot hook
rejects, and the process exits before anything is served. Nothing answers the health check, so the pod
fails to come up rather than coming up wrong. Where the api does start, every read of an access grant
fails instead: not only watching a screen, but opening a
terminal, the asset-scoped tools an assistant may use, and **Settings → Access** itself, because one
query now reads the two new columns for all of them. What that failure says is the database's own
complaint about a column that does not exist, not a tidy "try again in a moment". Under Argo CD the Job
is a Sync hook in wave 0 and runs on its own; applied by hand, run it and wait for it. On docker
compose none of that applies — the api container still applies the schema itself before it serves, and
refuses to serve at all rather than serving half of it. **Reload the app in every open browser tab
once the web image is out**: a tab still running the previous build does not know about the two new
switches, and saving an existing grant from it writes them off — an edit that does not mention a
switch turns that switch off, which is how a grant edit has always worked. That is also the one thing
that quietly undoes the widening above, and nothing puts it back: the pass runs once per instance, so
a grant saved from a tab that was open across the deploy loses both switches for good.
**One caution, stated because it is the kind of thing a changelog should not leave for a support
ticket.** The screen is captured — and the keyboard and mouse delivered — by a short-lived helper
process that Jarvis starts inside the machine's interactive Windows session, because a Windows service
cannot reach another session's screen or press a key into it at all. It runs as the service's own
account moved into that session rather than as whoever is sitting there, and it needs nobody to be
sitting there: an active console session with no one signed in is an ordinary target, and on a server
it is the usual one. It is started and stopped with the Jarvis session — four hours at the outside —
and it asks Windows for no privilege the service did not already hold. What it gains is reach rather
than privilege, and that is the part to weigh: a system account standing on an interactive desktop can
attach to the desktops Windows keeps for its own prompts, and can deliver a click into a window running
elevated. The second is deliberate — control that stopped at an administrator's own dialog would be
control that does not work — and of the first we take only the sign-in desktop of a machine nobody is
signed in to, which is the case this feature exists for. It is also the reason the notice can be drawn
where the person will actually see it. On a machine with unusually strict software-restriction policy,
that helper is a new executable path your policy has not seen before.
## 2026-09-27 — The install's last step no longer strands a first deployment `api 0.116.1` `web 0.141.2` ## 2026-09-27 — The install's last step no longer strands a first deployment `api 0.116.1` `web 0.141.2`
**The symptom.** On a first installation the wizard could stop on its last step, the vault key **The symptom.** On a first installation the wizard could stop on its last step, the vault key
+72 -11
View File
@@ -73,6 +73,13 @@ and different accounts on the far end. The choice is made before the shell opens
the session and in the audit trail, and every route is held to the same limits: the same idle and the session and in the audit trail, and every route is held to the same limits: the same idle and
duration ceilings, the same recording, the same retention policy. duration ceilings, the same recording, the same retention policy.
**And, on Windows, a screen.** Where the terminal gives you a shell on an enrolled machine, remote
desktop gives you what the person sitting at it is looking at. It reuses the terminal's permission
model and adds one thing the terminal has no equivalent of: **the person at the machine is always
told.** Each organisation chooses a standing banner or a request they must answer, there is no third
choice and no way to switch the notice off, and the session ends if the notice cannot be kept in
front of them. Nothing of the screen is recorded — see [The agent](#the-agent).
**Documents.** A conversation accumulates what the assistant writes: reports and runbooks in **Documents.** A conversation accumulates what the assistant writes: reports and runbooks in
Markdown, diagrams in Mermaid, tabular data as a workbook. What it writes is always a *source*, and Markdown, diagrams in Mermaid, tabular data as a workbook. What it writes is always a *source*, and
Jarvis renders the file on download — PDF and Word from prose, Excel and CSV from a workbook, HTML Jarvis renders the file on download — PDF and Word from prose, Excel and CSV from a workbook, HTML
@@ -290,14 +297,23 @@ marked installed by a backfill — so you will never see the install screen. Not
All three are the kind that produce confusing symptoms rather than clean errors. All three are the kind that produce confusing symptoms rather than clean errors.
- **Forward the WebSocket upgrade.** Two separate sockets ride `JARVIS_PORT`: the chat, on - **Forward the WebSocket upgrade.** Three separate sockets ride `JARVIS_PORT`: the chat, on
`/socket.io/`, and enrolled agents, on `/api/agents/ws`. Neither falls back to plain HTTP. Without `/socket.io/`; enrolled agents, on `/api/agents/ws`; and, for remote desktop only, one media
connection per live session on `/api/agents/ws/desktop`. None falls back to plain HTTP. Without
the upgrade the chat does not lose streaming — it never connects at all, and since the prompt the upgrade the chat does not lose streaming — it never connects at all, and since the prompt
itself travels over that socket, nothing sends. Presence and in-chat approvals go with it, and no itself travels over that socket, nothing sends. Presence and in-chat approvals go with it, no
agent can connect. agent can connect, and no screen can be watched. A proxy that forwards `/api/agents/ws` by exact
path rather than by prefix will pass the first two and silently fail the third, which reads as
"remote terminal works and remote desktop does not".
- **Give it a long read timeout** — 300s or so. A reasoning model can go 90+ seconds without emitting - **Give it a long read timeout** — 300s or so. A reasoning model can go 90+ seconds without emitting
a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather
than a timeout, which reads like a bug in Jarvis. than a timeout, which reads like a bug in Jarvis. The desktop media socket is legitimately silent
for far longer than that — a viewer whose tab is hidden pauses the stream, and nothing is sent
until the tab comes back — but it does not need a longer timeout, because the api pings it on a
beat of its own and a WebSocket ping is traffic as far as your proxy's read timer is concerned.
A proxy that drops idle **upgraded** connections on a separate, shorter clock is the one
configuration that breaks this; on nginx that is `proxy_read_timeout` and the ping already
covers it.
- **Let uploads through.** Attaching or uploading a file posts it to `/api/uploads/`, up to 10 MB - **Let uploads through.** Attaching or uploading a file posts it to `/api/uploads/`, up to 10 MB
each, and an image uploaded to a workspace travels with a copy of up to 3 MB prepared for the each, and an image uploaded to a workspace travels with a copy of up to 3 MB prepared for the
model. Your proxy must accept request bodies of at least **13,376 KiB** on that path (nginx: model. Your proxy must accept request bodies of at least **13,376 KiB** on that path (nginx:
@@ -416,16 +432,42 @@ Four things worth knowing about it:
recorded by default, encrypted under `VAULT_MASTER_KEY` and deleted on a retention policy you set recorded by default, encrypted under `VAULT_MASTER_KEY` and deleted on a retention policy you set
under **Settings → Organization → Terminal sessions**. Turning recording off stops the transcript, under **Settings → Organization → Terminal sessions**. Turning recording off stops the transcript,
never the audit entry. never the audit entry.
- **It carries a remote desktop, on Windows.** The same permission model, one level up: a person
with the right watches the signed-in session's screen from the browser. **Nobody is ever watched
invisibly.** Each organization picks one of two policies under **Settings → Organization → Remote
desktop** — a banner that stands on the screen for the whole session, or a prompt the person must
answer before anything is captured, which auto-accepts only when nobody is signed in. There is no
third option and no way to turn the notice off, which is the one decision in this feature that is
not configurable. The session ends the moment the banner cannot be kept visible. Nothing of the
screen is stored: there is no recording, no thumbnail and no still, so the audit entry is the
whole of the trail. Linux and macOS are not supported yet, and a session opened against one is
refused rather than silently downgraded.
**What an enrolled agent can do.** Inventory the machine, run commands, read, write and fetch files, **What an enrolled agent can do.** Inventory the machine, run commands, read, write and fetch files,
list and control services, list processes, update itself, and carry that shell. It runs as root on list and control services, list processes, update itself, carry that shell, and — on Windows — show
Linux and macOS and as LocalSystem on Windows, deliberately — its purpose is to administer the its screen. It runs as root on Linux and macOS and as LocalSystem on Windows, deliberately: its
machine. purpose is to administer the machine. Watching a screen is the one thing it cannot do from there,
because a Windows service has no access to the window station a person is signed in to; capture runs
in a short-lived helper the service launches into that session, and that helper is also what draws
the notice.
What the **assistant** does with that reach is bounded by the autonomy policy and the approval gates What the **assistant** does with that reach is bounded by the autonomy policy and the approval gates
above. **The interactive shell is not.** There is no command to inspect before a shell opens, so the above. **The interactive shell and the remote desktop are not.** There is no command to inspect
risk ceiling has nothing to weigh; it is gated instead by a permission and a per-machine switch on before a shell opens and no command at all behind a screen, so the risk ceiling has nothing to
the grant. Decide who holds those before enrolling anything you care about. weigh; both are gated instead by a permission and a per-machine switch on the grant, and neither is
ever opened by the assistant — a person opens them. Decide who holds those before enrolling anything
you care about. The desktop has one protection the shell does not, and it is not a permission: the
person sitting at the machine is told, every time, for as long as it lasts.
**Being told is not the same as being able to stop it, and the difference matters.** The notice is
click-through on purpose — it takes no click, it has no menu and it has no keyboard — so that it can
never sit between somebody and the work they are doing, and so that it cannot be closed by accident
or by a script. The cost of that choice is that the person at the keyboard cannot dismiss the viewer
from the notice itself. Their ways out are elsewhere: refusing the prompt under the ask policy,
before anything is captured; signing out, which ends the session with it; or an administrator
turning the feature off for the instance. What the banner guarantees is that watching is never
secret. It does not guarantee consent once it has started, and under the banner policy it is not
asking for any.
## Things worth knowing before you trust it with production ## Things worth knowing before you trust it with production
@@ -468,6 +510,23 @@ These are deliberate and documented rather than surprises waiting to be found.
something to watch rather than something to assume. Two API processes have been run together something to watch rather than something to assume. Two API processes have been run together
against one database and one Redis, and the manifests are published, but that is not the same against one database and one Redis, and the manifests are published, but that is not the same
thing as your machines depending on it. thing as your machines depending on it.
- **Remote desktop is the newest thing here, and its way out is a switch rather than a rollback.**
The api and the web roll back by pinning a digest, but an agent has already replaced its own binary
on every machine it runs on by the time you want it back — so the control that matters is
**Settings → Platform → Agents → Remote desktop**. Turning it off refuses new sessions and ends
every live one within 15 seconds, fleet-wide, without touching the interactive shell or anything
else the agent does. Know where it is before you need it. Two consequences worth holding onto: no
part of a screen is ever written anywhere, so if an incident needs a record of what was *seen*,
this feature cannot produce one after the fact. What the session record does hold is more than a
timestamp — who watched, which machine, which Windows session and account, which monitor, which
consent policy applied, whether consent was agreed and when, whether it was auto-accepted because
nobody was signed in to be asked, whether control was allowed, how many input events were accepted
and how many were dropped, and how it ended — so write a staff disclosure from that list rather
than from "no recording". Note what is *not* on it: there is deliberately no column saying the
banner was drawn. The banner is drawn by the agent on the far side of a websocket and the server
never learns whether it went up, so a field answering "was it shown" could only ever hold a guess;
what the row holds instead is which policy the session ran under. And it is Windows-only; a session opened against Linux or macOS is
refused outright rather than quietly degraded into something weaker.
- **There is no rate limiting on sign-in.** No throttler, no account lockout, and authentication - **There is no rate limiting on sign-in.** No throttler, no account lockout, and authentication
events are not audited. Credential stuffing is bounded only by the reverse proxy you put in front, events are not audited. Credential stuffing is bounded only by the reverse proxy you put in front,
which this repository does not ship. If your proxy can rate-limit one route, make it that one. which this repository does not ship. If your proxy can rate-limit one route, make it that one.
@@ -544,6 +603,8 @@ wrong on the screen that asks for it.
| An answer dies part-way through, every time | The proxy's read timeout is too short. | | An answer dies part-way through, every time | The proxy's read timeout is too short. |
| Everything works but one kind of thing cannot be created | A limit is full — the community edition's, or your licence's. The message names which. See below. | | Everything works but one kind of thing cannot be created | A limit is full — the community edition's, or your licence's. The message names which. See below. |
| An agent installer answers 503 | No agent build is published — the overlay is off. That is a supported state. | | An agent installer answers 503 | No agent build is published — the overlay is off. That is a supported state. |
| The remote terminal works and remote desktop never connects | The proxy forwards `/api/agents/ws` but not the paths under it. The media socket is `/api/agents/ws/desktop` and needs the upgrade too. |
| A remote desktop session opens and ends immediately | Almost always the notice. It ends by design if the banner cannot be kept on top, if the person refuses, or if nobody answers the request within the minute. The end reason on the session says which. |
The api takes about a minute on first boot, syncing the schema before it listens. `docker compose ps` The api takes about a minute on first boot, syncing the schema before it listens. `docker compose ps`
showing `health: starting` for that long is expected, not a fault. showing `health: starting` for that long is expected, not a fault.