Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

Atrium

Technical Reference Manual — the Peios web session host: the three-process architecture, logon and session jobs, the shell's state mirror, and the application contract.

Single-page view · as markdown

1.1 Overview

Peios / Advanced Peios / Atrium / Introduction

Atrium is the Peios web UI. A browser pointed at a Peios machine gets a login page; a successful logon gets a desktop — windows, apps, a terminal — served by that machine and running on it. Atrium is not a management panel over the system; the browser tab is the user's session.

Four decisions shape everything else in this manual:

  • The browser is a renderer. All session state — which windows exist, which is focused, the chord table — lives on the machine, in the user's session host. Every connected browser tab holds a mirror of that state and asks for changes; no tab owns anything. Two tabs, or a laptop and a phone, are two views of one desktop.
  • The shell is thin; applications are the product. The shell owns login, windows, focus, keybinds and the launcher, and nothing else. Everything a user opens is an application: a directory of web files with a manifest, drawn in an iframe, talking through one SDK (chapter 5).
  • Privilege is split across three processes. The one privileged process holds no network socket and parses no HTTP; the process that faces the network runs with every privilege deleted; the process that does the user's work is the user (chapter 2).
  • Atrium does not create user processes. Session hosts are submitted to peinit as jobs, with the logon token as the job identity — peinit is their parent, supervisor and record-keeper (§3.2).

Atrium listens on port 8080, unencrypted, and every browser-facing door is one HTTP origin. Transport security and per-application origins are not provided in this version.

1.2 Terminology

Peios / Advanced Peios / Atrium / Introduction

Three different things are reasonably called "a session", and Atrium keeps them distinct:

TermWhat it isOwned by
Logon sessionThe kernel's unit of identity: created by authd per successful logon conversation, identified by the token's LUID, spanning every process carrying that tokenKACS / authd
Interactive sessionOne running desktop: the atrium-session process and its state — windows, focus, applications, terminalsAtrium
Browser sessionOne browser's binding to an interactive session: a cookieatrium-server

They are one-to-one-to-one today — one login produces one logon session, one interactive session and one cookie — but the concepts diverge as features arrive: a second device attaching to an existing desktop adds a browser session without a logon; an unlock re-runs a logon conversation without touching the interactive session.

The rest of the vocabulary:

  • Shell — the chrome a logged-in browser renders: topbar, sidebar, Toolbox, window frames. One page, served by the session host.
  • Mirror — one connected shell's copy of the session state, kept current over a websocket (§4.1).
  • Window — one open application instance: an entry in the session's window list, and one iframe in every mirror.
  • Application (app) — an installed directory under /usr/share/atrium/apps/<id>/ with a manifest; what the Toolbox launches (chapter 5).
  • Session host — the atrium-session process of one interactive session.
  • The bus — the postMessage path between an application's frame, the shell, and the session host (§5.4).

2.1 The Three Processes

Peios / Advanced Peios / Atrium / Architecture

Atrium is three programs, split so that privilege, network exposure and user code never share a process.

ProcessIdentityStarted byRole
atriumdSYSTEMpeinit (service atriumd)Originates logons with authd, holds the session table, spawns the other two
atrium-serverSYSTEM with every privilege deletedatriumd (fork)The only network-facing process: TCP :8080, TLS-less HTTP, cookies, forwarding
atrium-sessionThe logged-on userpeinit (submitted job)One per interactive session: serves the shell, holds session state, runs applications

atriumd is deliberately the smallest of the three. It runs as SYSTEM because originating a logon takes that today — /run/logon.sock admits SYSTEM alone — and everything it does follows from holding that position: it conducts the PGSS Logon conversation, keeps logon tokens in its session table, submits session hosts to peinit, and relays the login exchange for the server. It parses no HTTP and never installs a token on anyone. If a feature appears to belong in atriumd, it belongs in one of the other two.

atrium-server is forked by atriumd at startup with a copy of atriumd's own token from which every privilege has been deleted (Token::restrict): the same user SID, so nothing about the filesystem changes, but nothing a compromised HTTP parser can spend. It asks the kernel for SIGTERM on parent death, so it cannot outlive atriumd. It holds exactly the routing view — cookie → session — and never sees a token.

atrium-session is not a child of Atrium at all. On a successful logon, atriumd submits it to peinit's jobs channel (PSPU §7) with the freshly minted logon token attached as the job identity — so peinit, the one process on the machine that installs primary tokens for others, is its parent, and the job record, cgroup and output capture come with that. The session host starts with no privilege and needs none: it is the user.

The processes an application starts — an exec command, a terminal's shell — are children of the session host, inside the user's logon session and the job's cgroup.

2.2 Sockets and Descriptors

Peios / Advanced Peios / Atrium / Architecture

Nothing in Atrium dials a rendezvous socket. Every internal link is a socketpair created by the process that owns the relationship, with one end handed down at spawn — trust is structural, and there is no path to clean up and no peer to verify.

  • atriumd ↔ atrium-server: one socketpair, created before the fork; the child finds its end on descriptor 3. It carries the control protocol — the login conversation relay, and the handover of each new session's socket. Frames are a 32-bit little-endian length prefix followed by one JSON document; a file descriptor, when one travels, rides as SCM_RIGHTS on the length prefix, so a frame and its descriptor cannot separate.
  • atriumd → atrium-session: the session's control socket, created by atriumd and attached to the peinit job submission as the descriptor named atrium-control; peinit places it on descriptor 3 of the session host. The server holds the other end.
  • atrium-server → atrium-session, per connection: for each browser connection bound to a session, the server creates a fresh socketpair, sends one end down the session's control socket, and pumps the connection's bytes through the other for the connection's life. To a session host, "accept a connection" means "receive a descriptor". A websocket is the same pump; after the upgrade it is just bytes.

The server rewrites each forwarded request's head before replaying it: the Cookie header is removed — the bearer never crosses into user space — and two headers are added, X-Atrium-Browser-Session (a random tag distinct from the cookie value, letting a session tell its browsers apart) and X-Atrium-Client-Addr. A session host can trust those headers for exactly one reason: nothing but atrium-server can reach it. The descriptors arrive over a private socketpair, the host listens on nothing, and that property is what the whole arrangement leans on.

atriumd watches each session host through the pidfd that peinit returns at submission, and reads how a session ended from peinit's job record rather than from wait.

3.1 Logging On

Peios / Advanced Peios / Atrium / Logon And Sessions

Atrium adds no authentication of its own. Logging on through the browser is a PGSS Logon conversation (PGSS §2) with authd, conducted by atriumd, with the browser standing where a terminal would.

A browser with no valid cookie gets the login page from atrium-server — the one page the server itself owns, since before a logon there is no session to serve anything. The page collects a username and posts it; from there:

  1. atriumd connects to /run/logon.sock and sends LogonStart, authored by atriumd itself: logon type Network, the username, and the client address as the unverified remote host. The browser proposes nothing.
  2. authd's credential requests are relayed outward as JSON — the page renders whatever prompts arrive, with no assumption that the answer is a password — and the answers travel back the same way.
  3. On AccessGranted, the token descriptor stays in atriumd's session table; it never travels further. atriumd submits the session host (§3.2), hands the server the session's socket, and the server mints a cookie.
  4. On AccessDenied, a retryable denial is shown to the browser as one uniform "Incorrect username or password"; the specific denial goes to the log. Non-retryable denials are shown as given.

The cookie is 256 bits from the kernel's entropy pool, hex-encoded, HttpOnly and SameSite=Strict. It is a bearer: whoever holds it is the session, which is why it goes into the cookie jar and nowhere else — not to session hosts, not into JavaScript. One cookie binds one browser to one interactive session; logging out discards both the cookie and the session.

Passwords cross the network unencrypted in this version, as does everything else; the deployment posture is a trusted network segment until TLS lands.

3.2 Session Hosts as Jobs

Peios / Advanced Peios / Atrium / Logon And Sessions

On a successful logon, atriumd submits one job to /run/services/peinit/jobs.sock (PSPU §7):

  • Identity: the logon token authd just minted, attached to the submission as a KACS_SCM_TOKEN. The kernel gates the attach and peinit duplicates the token to the job's primary — atriumd holds the token but never installs it, on itself or anyone.
  • Program: /usr/libexec/atrium/atrium-session, no arguments.
  • Descriptors: the session's control socket, named atrium-control, which peinit places on descriptor 3.
  • Environment: HOME, USER, LOGNAME, PATH, SHELL (from the logon profile; /bin/sh when the profile names none), plus ATRIUM_SESSION (the logon session id) and ATRIUM_DISPLAY_NAME. The working directory is the profile's home when that directory exists, / otherwise — a missing home starts a session rather than failing one.
  • Stop: stop_timeout of 10 seconds.

The response carries the job's pidfd, which joins atriumd's poll loop; a session host's death is noticed there, and the cause is read back from peinit's job record while it is retained. Logging out sends the jobs channel a stop — SIGTERM, then peinit's kill after the stop timeout, cgroup-wide.

A jobs-channel connection is opened per operation and dropped: peinit closes idle jobs connections, and a logon is rare enough that a kept connection would be found dead exactly when it was next needed.

Because peinit is the parent, session hosts survive an atriumd restart as processes — but atriumd's table and the server's cookies do not, so a restarted Atrium does not know them and users log in afresh. Reattachment through peinit's job listing is possible machinery that this version does not have.

3.3 Lifecycle

Peios / Advanced Peios / Atrium / Logon And Sessions

A browser closing changes nothing on the machine. The interactive session keeps running; the browser's cookie still names it; returning to the page resumes the same desktop. Detachment is free.

What ends an interactive session:

EventPath
LogoutThe server drops the cookie and session link, tells atriumd, and atriumd stops the peinit job
The session host exitsatriumd's pidfd poll notices; the token is dropped, which releases the logon session
atriumd exitsatrium-server dies with it (parent-death signal); session hosts see their control socket close and exit
The machine rebootsEverything above

The logon token is held by atriumd for the session's life and dropped when the session ends; dropping the last token reference is what retires the kernel's logon session.

On the browser side, the shell treats its websocket as expendable. Requests made while the socket is connecting are queued and flushed on open, so a click during the first half-second is not lost. Reconnection backs off from 300 ms to 3 s, retries immediately when the tab regains focus or the network returns, and every third failure asks /api/whoami over plain HTTP whether the session still exists — a stale cookie (the machine rebooted; the session was logged out elsewhere) answers with a redirect, and the shell reloads onto the login page rather than retrying a socket that can never connect.

4.1 The Mirror

Peios / Advanced Peios / Atrium / The Shell

Each browser tab's shell opens one websocket to its session host at /ws (forwarded by atrium-server like any other connection). The session sends a snapshot — the window list, the focus, the chord table — and from then on events; the shell sends requests.

State flows one way. A shell never changes anything locally: clicking a tile sends launch, and the tab that asked learns of the resulting window the same way every other tab does, from the broadcast. That is what makes two tabs agree without trying: there is nothing to reconcile, because nothing but the session ever decides.

The session pushes facts, not markup. An event names what changed — window.opened, focus, window.closed, window.minimized, window.titled — and each shell works out the DOM consequences. The constraint behind this is the iframe: a window's frame is a live application, and re-parenting or re-rendering it reloads the app, so frames are created once and only ever shown or hidden.

Requests that fail — an unknown app, a closed window — produce an error message to the asking tab alone. Errors are not state, and no other mirror hears them. The same routing carries application request/reply traffic (§5.4): replies and streams go only to the tab whose frame asked.

Window ids are minted by the session. Titles are session state too — an application renaming its window (§5.3) changes every mirror's sidebar at once.

The websocket implementation in the session host is a deliberately small subset of RFC 6455: text, close and ping frames, client masking required, no fragmentation or extensions. Every write to one connection is serialised through one lock — a pong interleaving with a broadcast mid-frame would corrupt the stream, and did, once.

4.2 Windows

Peios / Advanced Peios / Atrium / The Shell

The session keeps a flat list of windows and at most one focused window. Each window belongs to an application, has a session-minted id and a title, and is either visible or minimised.

Opening. launch names an app. If the app already has a window, the most recent one is focused instead — one window per application is the default, and a second is the deliberate ask launch { new: true } (the context menu's "Open in new window"). Windows of one application are numbered by the session as they multiply: "Terminal", "Terminal 2".

Focus. Focusing a minimised window restores it first (window.restored, then focus). Closing or minimising the focused window passes focus to the most recently opened still-visible window, or to none — and a tab showing no focused window shows the Toolbox.

Minimise is session state like everything else: the frame stays loaded in every tab, hidden; the sidebar entry dims. Whether a given tab is looking at its windows or at the Toolbox is the one piece of state that is deliberately per-tab.

Chrome. The focused window sits under a bar with its application's glyph and title on the left and two lights on the right: yellow minimises, red closes. Each open window has its own sidebar entry — running dot, dimmed when minimised, highlighted when focused; middle-click closes it. Pinned applications without windows show a single launcher entry.

An application's frame is served from the session host (/apps/<id>/…), created hidden, and revealed by the SDK handshake or by a 1.2-second grace for pages that never load the SDK (§5.3).

4.3 Keybinds

Peios / Advanced Peios / Atrium / The Shell

Keyboard chords have three owners, and the boundaries are physical rather than chosen:

  1. The browser and OS own what they own — Ctrl+W, Ctrl+T, Alt+Tab, F11 on most browsers — and a web page never sees some of them. Atrium's defaults avoid the common ones.
  2. The shell owns the reserved chords: global desktop actions that work everywhere, including while an application frame has focus.
  3. The focused application owns everything else.

The chord table is session state, shipped in the snapshot as keymap: a list of { chord, action }. Chords are written as sorted modifiers plus a key — Ctrl+Alt+T, key from the keyboard event's key value, single characters uppercased. Actions are strings; every action today is a shell action, and the string form is what leaves room for application-owned globals later without a schema change.

ChordAction
Ctrl+Kcommand-bar
Alt+Ttoolbox
Alt+Wclose-window
Alt+Mminimize-window
Alt+1…Alt+9focus-window-1…9
Ctrl+` cycle-window

In the shell's own document, a capture-phase listener matches the table and runs the action. Inside an application frame, the shell cannot hear keys at all — so the SDK carries the shell's half inward: it receives the reserved chord list with ready, captures those chords at its own capture phase, prevents the default, and posts the chord back to the shell, which runs the action. Everything else passes to the application untouched.

The arrangement is cooperative. A frame that does not load the SDK swallows global chords while focused — the documented cost of not integrating — and a hostile frame could at worst do the same, since the shell acts only on chords present in its own table.

5.1 What an Application Is

Peios / Advanced Peios / Atrium / Applications

An Atrium application is a directory of web files with a manifest. The contract has two independent axes rather than kinds:

  • Where the UI comes from. Either the package ships static files and the session host serves them (entry, the default), or the frame points at a URL Atrium does not serve — an application that is its own web server, or someone else's.
  • What runs behind it. Nothing; commands the session runs on request (exec, §5.5); a terminal (pty, §5.5); or, in a later version, a long-running backend process.

The useful shapes fall out of the grid: a pure page (About), a wrapper app — static UI whose buttons run allowlisted commands, most of the Toolbox's natural contents — a terminal, and an embedded external web application.

Two properties hold across all of them:

  • Everything an application does on the machine happens as the user, inside the session host's process tree and the session job's cgroup. The manifest's capabilities narrow what an application may ask the session to do; they never widen who it is.
  • An application that loads the SDK is a citizen; one that does not still works. The SDK is how a frame gets the handshake, the theme, requests and shortcuts. A plain page without it is shown after a grace period and swallows global chords while focused — degraded, deliberately, rather than broken.

5.2 The Manifest and the Catalogue

Peios / Advanced Peios / Atrium / Applications

Applications install under /usr/share/atrium/apps/<id>/, one directory per application, shipped by ordinary packages. Nothing runs at install and nothing registers: presence is the catalogue. The session host reads the directory on every request, so an installed package's app appears on the next Toolbox load.

The id is reverse-DNS (org.peios.terminal), lowercase letters, digits, hyphens and dots, and the directory name is the id — a manifest whose id disagrees with its directory is a broken package and is skipped, with a line on the session's log.

manifest.toml:

FieldMeaning
idThe application id; equal to the directory name
nameDisplay name
descriptionOne line, shown on the tile
categoryGrouping label for the launcher; optional
iconA file in the directory, served as the icon; optional
glyph, colorA built-in stroked glyph name and chip colour, used when no icon ships
entryThe document the window loads; default index.html
[capabilities]What the app may ask the session for (§5.5)

The session host serves /apps/<id>/<file> from the application's directory, rejecting any path that could escape it, and serves the catalogue as JSON at /api/apps with each entry's entry and icon already resolved to URLs.

Two stylesheets are served to every application from the shell's own assets: /shell/tokens.css, the design system's variables plus body ground defaults — an application that links it and styles with var(--c-*) matches the shell in both themes with no colours of its own — and the SDK at /shell/sdk.js.

5.3 The SDK

Peios / Advanced Peios / Atrium / Applications

/shell/sdk.js is a dependency-free ES module. Importing it sends hello to the shell; everything else hangs off the exported atrium object.

The handshake. A window's frame is created hidden. When the SDK's hello arrives, the shell answers ready with the window id, the current theme, the user (username and display name) and the reserved chord list, and reveals the frame; atrium.ready() resolves with that context. A page that never sends hello is revealed by a 1.2-second grace instead. The SDK sets data-theme on the document root at ready and keeps it current, which is what drives tokens.css variables; atrium.on('theme', fn) hears changes.

Requests. atrium.request(body) sends one JSON object to the session and resolves with the reply. Kinds that stream — exec — deliver interim parts to an optional second argument before the final reply. Convenience wrappers:

  • atrium.exec(cmd, onOutput) — run an allowlisted program (§5.5); onOutput(text, stream) receives stdout and stderr as they happen; resolves with { exit_code, exit_signal, truncated }.
  • atrium.pty({ cols, rows, onData }) — open a terminal (§5.5); returns { write, resize, done }.

Window verbs. atrium.setTitle(s) renames the window — session state, visible in every tab's bar and sidebar. atrium.close() asks the shell to close this window.

Shortcuts. atrium.shortcuts.on(chord, fn) registers an application-local chord, active while the window has focus. Reserved chords never fire here; they are captured first and forwarded to the shell (§4.3).

The declarative layer. For wrapper apps, the SDK wires clicks on any element carrying data-atrium-exec:

<button data-atrium-exec="peipkg list" data-atrium-target="#out">
  Installed packages
</button>
<pre id="out"></pre>

Output streams into the target as text; the exit status lands on the target as a data-exit attribute; the trigger is disabled while the command runs. The command string is split on whitespace with no quoting — an invocation that needs quoting has outgrown the attribute and uses atrium.exec.

5.4 The Bus

Peios / Advanced Peios / Atrium / Applications

An application frame holds no socket and no cookie. Its one channel is postMessage to the parent shell — the only browser mechanism that survives every isolation boundary Atrium may later put around a frame (foreign origins, sandboxed frames with an opaque origin). The path of a request:

app frame → postMessage → shell → websocket → session host

The shell identifies the sending frame by its contentWindow and stamps the window id onto the relayed message. An application cannot name its window and therefore cannot speak as another; the session never authenticates applications, because the shell has already vouched for which window is talking and the session knows which application owns that window.

Replies and streams route the same way in reverse — to the one tab whose frame asked, then by postMessage into that frame — correlated by a request id the SDK mints per call.

The bus is a control plane. A round trip costs postMessage in and out plus the websocket, which is invisible next to the network for anything a human triggers, and measured throughput is comfortable for terminal-scale streaming. The recorded posture is control on the bus, bytes on a channel: an application that ever ships sustained bulk data gets a dedicated channel then, authenticated by a per-window token handed over the bus — machinery that exists as a design, not in this version, because nothing has needed it.

5.5 Capabilities

Peios / Advanced Peios / Atrium / Applications

An application's manifest declares what it may ask the session to do. The session host reads the manifest from disk at each request — the installed manifest is the truth, and there is no registration to go stale. An application with no [capabilities] table can render, talk on the bus, and nothing else.

5.5.1 exec #

[capabilities]
exec = ["peipkg", "svctl"]

{ kind: "exec", cmd: [argv…] } runs a program as the user. The first argv element is matched against the list — "*" allows any program — and a refusal names the application, the program and the manifest, so a denial reads as an explanation rather than a mystery.

No shell is involved: argv is executed as given, in the user's home, with the session's environment. Output streams back as it happens, stdout and stderr separately labelled; the final reply carries the exit code or signal. Two limits protect the session: one megabyte of streamed output and five minutes of runtime, past either of which the process is killed and the reply says truncated.

5.5.2 pty #

[capabilities]
pty = true

{ kind: "pty-open", cols, rows } opens a pseudo-terminal running the user's $SHELL as a session leader with the slave as its controlling terminal, TERM=xterm-256color. Output streams like exec's; input and window sizes travel as fire-and-forget pty-input / pty-resize messages naming the opening request; the opening request's reply arrives when the shell exits. Closing the window hangs up the terminal's whole process group.

Capabilities narrow what may be asked; identity never changes. Everything either capability runs is the user, in the session's process tree, indistinguishable from work the user did at a console — which is also why the model is honest: an application granted exec = ["*"] has been granted the user, and a manifest is the place that grant is visible.

6.1 Cannot Log In

Peios / Advanced Peios / Atrium / Failure Modes

A browser logon crosses four systems, and the error surface says which one refused.

"Incorrect username or password" is authd refusing the credentials — the uniform rendering of every retryable denial. The specific denial is on atriumd's log (kmsg-mirrored, so the serial console has it).

"Could not start a session" means the logon succeeded but the session host did not start: atriumd's job submission failed. atriumd's log carries peinit's refusal — BAD_TOKEN, a jobs-socket error, or a failed launch, with the job's cause when one was created. The logon token is dropped on this path, so nothing lingers.

"Session host unavailable" on the login page means atrium-server could not reach atriumd at all — atriumd is down or restarting, and peinit's view of the atriumd service is the place to look.

Nothing loads at all: nothing is listening on 8080 (the service is down), or the network path is wrong — on a QEMU dev image the port is user-mode-NAT forwarded and reachable only through the host's forward, not the guest address.

A logon that succeeds but lands on a desktop that immediately bounces back to the login page is a session that died at birth: the pidfd noticed the host exit before the first request arrived. peinit's job record holds the exit.

6.2 The Shell Loses the Session

Peios / Advanced Peios / Atrium / Failure Modes

The shell's websocket drops for ordinary reasons — the machine rebooted, the laptop slept, the service restarted — and the shell is built to make most of them invisible.

While disconnected, requests queue rather than drop; the browser console notes each (session: queued until connected). Reconnection backs off 300 ms → 3 s, and a tab regaining focus or the network coming back retries immediately. Each websocket close is logged to the console with its code, which is the first thing worth reading when a shell seems dead.

Every third failed attempt, the shell asks /api/whoami whether the session behind its cookie still exists. A redirect answer means it does not — the machine rebooted, or the session was ended elsewhere — and the shell reloads, landing on the login page. Without that check a tab surviving a reboot would retry its websocket forever, since its cookie can never bind again.

What is lost across an interactive session's death is the session: open windows, terminals and their processes. What is never lost is anything the browser held alone, because the browser holds nothing alone.

If windows open but an application's frame stays blank past the handshake grace, the failure is inside the frame — its console (the browser's, for that frame) has the SDK's report, and the session host's log has the serving side.

6.3 Applications That Fail

Peios / Advanced Peios / Atrium / Failure Modes

A missing tile. An application that should be in the Toolbox and is not has a manifest problem: unparsable TOML, or an id that does not match its directory. Both are skipped with a line on the session host's log naming the file.

A capability refusal. An exec for a program outside the allowlist, or a pty-open without pty = true, is answered with an error naming the application, the request and the manifest — delivered to the asking frame, where the SDK rejects the promise. The declarative layer prints it into the target element; the Terminal shows it in place of the "shell exited" banner.

A command that dies or floods. exec's reply distinguishes an exit code, a signal, and truncated: true — the last meaning the session killed it at the output cap or the timeout, and the record of which is on the session log. Output already streamed stays streamed.

A terminal that exits instantly is the shell exiting, and the banner carries the refusal when there was one instead of an exit. A pseudo-terminal that cannot be opened at all reads as openpty failing in the message; on Peios that has one historical cause worth knowing — a devpts mount without its synthesised-SD policy, under which every slave is unopenable (peinit TRM §2.3).

One tab wrong, others right points at the mirror rather than the session: the state is per-session, but delivery is per-tab. A reload of the odd tab resynchronises it from the snapshot; the console's websocket log says what it missed.

Appendix A Message Reference

Peios / Advanced Peios / Atrium

A.1 The mirror websocket (/ws) #

One JSON object per text frame. Shell → session:

MessageFieldsEffect
launchapp, new?Open (or focus) an application
focusidFocus a window, restoring it if minimised
minimizeidMinimise a window
closeidClose a window
set-titleid, titleRename a window (relayed from its app)
appwin, req, bodyAn application request, shell-stamped

Session → shell:

MessageFieldsAudience
snapshotwindows, focus, keymapThe connecting tab
window.openedwindowEvery tab
window.closedidEvery tab
window.minimized / window.restoredidEvery tab
window.titledid, titleEvery tab
focusid (nullable)Every tab
app.replywin, req, bodyThe asking tab
app.streamwin, req, bodyThe asking tab
errormessageThe asking tab

A.2 The frame bus (postMessage) #

App → shell: hello {v} · set-title {title} · close · request {req, body} (req 0 marks fire-and-forget: pty-input, pty-resize) · chord {chord} (a captured reserved chord).

Shell → app: ready {window, theme, user, reserved} · theme {theme} · reply {req, body | error} · stream {req, body}.

A.3 Application request kinds #

KindCapabilityReply
system-info—Hostname, kernel, uptime, user, logon session, host pid, Atrium version
execexec allowlistStreamed output, then {exit_code, exit_signal, truncated}
pty-openptyStreamed output; reply on shell exit
pty-input, pty-resizeptyNone (fire-and-forget)

Appendix B Constants and Paths

Peios / Advanced Peios / Atrium

B.1 Network and sockets #

WhatValue
ListenerTCP 0.0.0.0:8080, plain HTTP (atrium-server)
Mirror websocketGET /ws on the same origin
Control descriptorsDescriptor 3 in atrium-server and atrium-session
Logon authority/run/logon.sock (client)
Jobs channel/run/services/peinit/jobs.sock (client, one connection per operation)

B.2 Filesystem #

WhatWhere
Binaries/usr/sbin/atriumd, /usr/libexec/atrium/atrium-server, /usr/libexec/atrium/atrium-session
Applications/usr/share/atrium/apps/<id>/manifest.toml
Service definition/usr/share/regim/atriumd-service.reg (applied by an image's autoapply)
Shell assetsServed at /shell/…: shell.js, shell.css, icons.js, sdk.js, tokens.css — compiled into atrium-session

B.3 Session host environment #

HOME, USER, LOGNAME, PATH, SHELL, ATRIUM_SESSION (logon session id), ATRIUM_DISPLAY_NAME. Applications' exec children inherit the session host's environment.

B.4 Limits and defaults #

WhatValue
Cookie256-bit random, HttpOnly, SameSite=Strict
exec output cap1 MiB, then kill + truncated
exec runtime cap300 s
Frame handshake grace1.2 s
Websocket reconnect300 ms × failures, capped 3 s; liveness check each 3rd failure
Session job stop timeout10 s
Window title cap120 characters
pty terminal typexterm-256color

B.5 Development overrides #

ATRIUM_LISTEN, ATRIUM_APPS_DIR, ATRIUM_SERVER, ATRIUM_SESSION (binary path), ATRIUM_JOBS_SOCKET; atriumd --unrestricted runs the whole stack under one identity on a KACS-less host. The atrium source tree's tools/dev/ holds the fake-atriumd harness and browser drivers these serve.

Peios Learn — documentation for the Peios project.

Built with Trail.