= CALYPSO: An Intent Layer and Session Daemon for ChRIS
Rudolph Pienaar <rudolph.pienaar@gmail.com>
0.1, 2026-07-06: Initial draft
:doctype: article
:toc: left
:toclevels: 3
:sectnums:
:sectnumlevels: 3
:icons: font
:status: DRAFT (living document; updated as design conversation progresses)

NOTE: Stages one and two are shipped. CALYPSO is the identity-keyed session host;
the natural-language intent layer and the network-facing multi-machine service
remain forward work. The engineering appendix preserves the original construction
plan and should be read as a historical sequence, not current implementation status.

[abstract]
== Abstract

This document specifies CALYPSO, an intent interpretation layer and session daemon for the ChRIS distributed computing platform. CALYPSO transposes the results of a prior design study (ARGUS) onto production infrastructure: the CUBE backend assumes the role of sole state authority, the mise software stack (cumin, salsa, chili, chell) supplies the deterministic execution substrate, and CALYPSO itself provides natural-language intent resolution and session management above them. The design commits to a terminal-central user interface: a single shell grammar, available identically on the command line and inside a future web console, is the primary interaction surface, and graphical elements are renderings of the same session rather than a parallel control path. We argue that this architecture removes the Collection+JSON client burden from user interface development entirely, improves rather than degrades interactive latency for compound operations, and preserves the central safety doctrine of the design study: deterministic resolution precedes probabilistic interpretation, and workflow truth is always materialized platform state, never an assertion made by a language model.

== What CALYPSO is

CALYPSO is where the session lives when nobody is looking at it.

That is the load-bearing idea and the rest follows from it. The session is the noun — a login, a working directory, a process cache, a PACS replay index, the feeds being watched — and CALYPSO is what holds it, keeps it alive, and lets surfaces attach to it.

The closest familiar thing is a *Jupyter kernel*. The kernel holds the namespace and does the work; notebooks and consoles are front-ends that attach to it and render. The mapping is direct: `brasa` is the runtime, CALYPSO is the kernel process, `chell` and ARGUS are front-ends. For a reader who has not met Jupyter, `tmux` serves: a session that survives the window closing and can be reattached from somewhere else.

Four consequences, which are the reasons it exists.

*Work outlives its viewer.* A topology walk over 58,000 nodes keeps walking when the browser closes. Nothing is lost on reload because nothing that mattered was in the tab.

*Several surfaces see one truth, not copies.* A terminal and a web console are not two clients synchronising; they are two projections of one session. `cd` in one moves the other. Copies would need reconciliation, and reconciliation is where multi-client interfaces go wrong.

*A session has an identity and an address.* One daemon per CUBE login, found by berth rather than by port, so "your session" is an object with a name.

*The engine can live where the data must stay.* Because a surface is thin and attaches over a wire, the engine can sit inside a hospital's trust boundary while an operator drives it from outside. This is the property that matters most in the long run, and it has nothing to do with browsers.

CALYPSO is also where policy lives, which is the part most easily missed. Lines that change the same piece of the session take turns. A pane's silent refresh may never interrupt an operator with a question. A hosted session will not touch the machine it runs on unless told to, in tiers. No surface has a say in any of it.

What CALYPSO is not: a web server, though it will serve ARGUS when a bundle is to hand; a message broker, since there are no peers, only viewers of one engine; multi-tenant, since one daemon serves one identity.

In one sentence: *chell is a surface, ARGUS is a surface, and CALYPSO is the session they are surfaces of.*

== Introduction

ChRIS is an open-source distributed computing platform for medical imaging and analysis <<chris>>. Its backend, CUBE, exposes all platform state (feeds, plugin instances, files, pipelines) through a REST API whose wire format is Collection+JSON <<cjson>>, consumed by clients through the `@fnndsc/chrisapi` JavaScript library <<chrisapi>>. Practical experience across multiple user interface efforts has shown that this client burden is substantial. Collection+JSON traversal is verbose, the published typings lag the live API surface, and compound operations decompose into long chains of sequential requests. Each new client has historically paid this cost again.

The mise project <<mise>> paid the cost once, deliberately, for the command line. Its layered stack (the Sandwich Model) confines every interaction with `chrisapi` to a single adapter seam in its lowest layer, and each layer above works exclusively with typed domain objects. BRASA hosts the deterministic engine; ChELL presents it as a familiar Unix shell, with data as a filesystem, plugins as executables, and running jobs as `/proc` entries; CALYPSO can host that same engine for remote sibling surfaces. The stack began as a command-line-only system with its engine fused to readline, but the first two construction stages below have since separated and shipped the engine and session host.

In parallel, the ARGUS design study <<argus>> explored, against a simulated platform, what a user interface looks like when an embedded intelligent terminal is the primary surface rather than a peripheral chat widget. The study produced a working prototype and, more durably, a set of architectural commitments and a named component: CALYPSO, the intent layer that mediates between natural language and deterministic execution. The study also inherited an earlier line of thought from the ChRIS Intent-Action Service proposal <<ias>>, which first formalized the principle that clients should speak in intent while a distinct orchestration layer resolves intents into executable action sequences.

This document records the synthesis of these three lines: CALYPSO, rebuilt on the mise stack against a real CUBE, as the foundation for a terminal-central ChRIS user interface. The design study itself is concluded and its repository frozen as the record of the reasoning; what follows is the forward specification.

=== The name

CALYPSO expands recursively to _CALYPSO Accepts Language, Yielding Permitted Shell Operations_, but the name is chosen for its harbor sense. In the _Odyssey_, Calypso keeps the island where the voyager finds haven; the name is the Greek word for "to conceal," which this project keeps but turns around. HARBOR — the operational effort this design serves — is that haven for the ChRIS operator, and CALYPSO is the keeper at its edge: the layer between the operator and the open water, the Collection+JSON sprawl and the complexity of a federated backend. What CALYPSO conceals is the friction, never the outcome. A harbor shelters without holding: the work is left as materialized, verifiable state, free to leave and return to — CALYPSO the harbor one passes through, never the ground one stands on. That last line is not ornament but the governing constraint restated: CALYPSO is the interaction layer and CUBE is the state authority, and the two are never confused.

== Background

=== ChRIS and CUBE

CUBE is the authoritative store for all durable platform state. A feed and its tree of plugin instances constitute a materialized computation graph: the existence of an output file at a given path within a feed is proof that the producing computation ran, and the path itself encodes lineage. This property, native to ChRIS, is foundational for everything that follows, because it means the platform already provides what the design study had to simulate: provenance-carrying, multi-client, durable state.

=== The mise stack

mise is a monorepo publishing four packages. cumin is the infrastructure layer and the sole importer of `chrisapi`; its adapter module is an anti-corruption seam that converts Collection+JSON responses into typed domain objects and confines all unsafe casts to one auditable file. salsa provides shared application logic, most importantly a virtual filesystem (VFS) dispatcher that projects CUBE state onto navigable paths such as `/proc` and PACS query trees. chili is the command and presentation layer, with a deliberate separation between command modules that return typed models and view modules that render them as text. chell is the interactive shell: a dispatch table of builtins, a parser handling pipes, redirection and wildcards, and a REPL. chili was designed to be driven in-process by a host without spawning a subprocess; this hosting pattern is load-bearing in the present design.

=== The ARGUS design study

ARGUS prototyped an operator console for a simulated federated learning platform. Because no real backend existed, the study constructed an in-memory virtual computer (filesystem, shell, plugin host) as its state substrate, and layered CALYPSO above it: a kernel that routes user input first through deterministic matching and only then through a language model, validates any model output against the vocabulary of commands and entities that actually exist, and returns a structured response envelope rendered identically by a CLI and (prospectively) a web surface. A session server exposed this core over WebSocket with a session bus broadcasting every interaction to all connected surfaces.

=== Doctrine carried forward

Four commitments from the study survive unchanged and govern this design. First, precedence of truth: deterministic resolution always precedes probabilistic interpretation, and language-model output is a proposal to be validated, never an action in itself. Second, data-state computation: workflow progress is proven by materialized artifacts, not asserted by any controller, and in the present design the materialized substrate is CUBE. Third, CALYPSO is the interaction layer and never the state authority. Fourth, the crash test: termination of the CALYPSO process at any moment must lose no workflow truth whatsoever; a reconnecting client re-derives everything from platform state.

=== Related systems

The composition specified here has, to the authors' knowledge, no direct precedent in the wild. Its components, however, are individually well proven, and this section records the nearest relatives, both as evidence for the design's soundness and as reference reading for its implementation.

The closest structural relative is the Jupyter kernel architecture <<jupyter>>. A Jupyter kernel is an engine daemon serving multiple simultaneously attached frontends over a correlated message protocol, with execute and completion requests, streamed output channels, and results carried as bundles holding several representations of one value so that each frontend renders what it can. That is, point for point, the topology, streaming model, and dual rendered-plus-model envelope of this design, arrived at independently. The Jupyter messaging specification is recommended reading; its interrupt delivery informed CALYPSO's correlated foreground cancellation.

The separation of typed models from rendered views has its deepest precedent in PowerShell <<monad>>, whose pipeline carries structured objects and defers rendering to the edge, and whose remoting serializes those objects across a session boundary. The projection of platform state onto a navigable filesystem is Plan 9 doctrine <<plan9>>: the modern `/proc` descends from it, and FUSE mounts of cloud services are its everyday contemporary instances. The engine-on-server, thin-surface-in-browser topology for a working tool is validated at scale by VS Code's remote server <<vscode>> and by the browser terminals of the major cloud consoles, each of which drives real infrastructure from an embedded terminal. Finally, the intent posture of the third stage, in which a language model proposes a command and a human confirms before anything consequential runs, is now shipping in mainstream AI-assisted terminals (Warp, GitHub Copilot CLI, Amazon Q); the present design is more disciplined than those, validating proposals against live platform vocabulary and gating on declared mutability, not less.

What lacks precedent is the whole: a terminal-central operator console for a federated medical compute platform, with an artifact-grounded intent layer and panels rendering the same session the terminal drives. The reading of this document is that the absence reflects the sparsity of serious user interface work in this domain rather than evidence against the composition, and the component precedents above are the argument.

== Architecture

=== The transposed triad

The design study's role vocabulary maps onto production components as follows.

[cols="1,2,2",options="header"]
|===
| Role | Design study realization | Production realization

| State authority
| In-memory virtual filesystem owned by the session server
| CUBE

| Deterministic substrate
| Simulated shell, commands, and plugin host
| The mise stack (cumin, salsa, chili, chell engine)

| Intent layer
| CalypsoCore kernel within the prototype
| CALYPSO package within mise

| Surfaces
| TUI client; web client designed but unbuilt
| chell CLI and a future ChRIS web console, as siblings
|===

The essential observation is that mise is the substrate the design study had to fake. The study built a virtual computer so that intent resolution would have something deterministic to stand on; mise is that same shape of system, with the difference that its filesystem projection is backed by a real platform and its executables really run.

.The transposed stack
----
        chell CLI                          web console
        (surface)                          (surface)
            │                                  │
            └────────────────┬─────────────────┘
                             │  websocket: wire contract
                    ┌──────────────────┐
                    │  CALYPSO daemon  │  brain: fast path, compiler, guard
                    │                  │  switchboard: session bus
                    └────────┬─────────┘
                             │  in-process
                    ┌────────┴─────────┐
                    │   chell engine   │  dispatch, session, completion
                    │   chili          │  commands (models) + views (text)
                    │   salsa          │  VFS providers, application logic
                    │   cumin          │  adapter seam to chrisapi
                    └────────┬─────────┘
                             │  REST: Collection+JSON, confined
                             │        to the cumin adapter
                    ┌────────┴─────────┐
                    │       CUBE       │  state authority
                    └──────────────────┘
----

=== The session daemon

CALYPSO runs as a calypso session hosting two concerns and deliberately excluding a third. It hosts the brain: intent interpretation, deterministic fast-path routing, language-model orchestration, guard validation, and the credentials these require. It hosts the switchboard: a WebSocket endpoint serving sessions to connected surfaces, with a session bus that broadcasts each interaction (input, resolution, response) to every surface attached to the same session, so that a command issued in the CLI is immediately visible in the web console and vice versa. It does not host durable state. Anything durable that CALYPSO produces (session transcripts, scaffolded code, workflow markers) is written through to CUBE as ordinary files in user space, where any client can inspect it with no CALYPSO involvement. Calypso's in-memory state is limited to the ephemeral interactive session: working directory, conversational context, pending operations. This is what makes the crash test passable.

Reattachment follows a three-tier continuity model matching what each kind of state actually is. Working context (current directory, current feed, the CUBE connection) survives everything for free: it is already persisted through the existing context files and rehydrated on daemon start exactly as a fresh CLI launch rehydrates today, and workflow truth lives in CUBE regardless. Scrollback is a bounded in-memory courtesy: a per-session ring buffer of recent envelopes replayed to an attaching or reconnecting surface so it does not join blind; a calypso session restart loses the buffer, which is correct, because scrollback is presentation rather than truth, and a durable record is available the doctrinal way, as an opt-in session transcript materialized to CUBE. Conversational context (the intent layer's anaphora window) is ephemeral by design: bounded, in memory, lost on restart at the cost of one re-phrasing. Full session persistence in the manner of a terminal multiplexer was considered and rejected; a multiplexer must persist because a shell process is state, and the entire point of this design is that the session holds no truth worth that machinery.

Calypso embeds the mise stack in-process. A headless chell engine (the dispatch table, parser, session, and VFS, separated from the readline surface) executes commands exactly as the CLI does today, over cumin's existing connection and cache layers.

Lines within a session run side by side unless they change the same piece of it (#1273). Before a line runs, the kernel reads off it what it holds: nothing for a read (`ls`, `cat`, `tree`, `grep`), the working directory for `cd`, the cohort for `gather`, and the whole session for any command that has not said (`pull`, `play`, a game, a host shell). A line holding nothing runs beside anything; lines holding a piece in common take turns, first come first served, and a later line never overtakes an earlier one it conflicts with, so a stream of reads cannot starve a writer. A listing read in a browser therefore comes back while a PACS pull typed in a console is still working, and a `cd` waits for the pull, exactly as every line once waited. Each line keeps its own slot: its exit status, its pipe input, its cancellation, its last answer (per surface), and the working directory it stands in, read once when it first asks, so a read that began in one folder finishes there whatever a `cd` beside it did. Variables are not a piece: storing one is a single step nothing comes between. Concurrency across sessions is unaffected and belongs to the tenancy model.

A line ended with `&` runs as a joblet (#1273): the runner keeps it at `/proc/joblets/<N>`, its output goes to `/proc/joblets/<N>/log` rather than into a console someone is typing in, and its surface hears `[1] pull … &` when it starts and `[1]+ Done  pull …`, `[1]+ Failed (exit 2): <its last error line>` or `[1]+ Stopped  pull …` when it ends (everyone hears it once that surface has gone). A joblet is a subshell: it stands where its line was typed and starts with that line's variables, and its own `cd`s and assignments stay inside it; what lives in CUBE, the cohort included, is shared, and the joblet holds what its line changes, in the same lane as every line. One that needs an answer pauses, gives up what it holds, and says so (`[1]+ Waiting for an answer: … · fg %1 to answer`); `fg %1`, from any surface, asks it there and follows the log until the joblet ends, and Ctrl-C stops following, never the joblet. `joblets` lists them (the word `jobs` is ChRIS's, for plugin instances), `kill %1` stops one, `wait %1` follows one. Joblets belong to the session: they outlive the surface that started them, and a daemon restart ends them, their logs kept, each listed as ended by restart. A joblet runs the kernel's lines only: what needs a surface's machine (a host shell, an editor, a pipe through a host program) has no surface to run on and refuses.

A command that waits gives up what it holds while it waits, because waiting is not working: one asking a person a question, and one following a task (`domain build` following its build, `wait` following a job). A follow steps out between the task's lines and takes its turn again to print each one, so a pane's reads and other lines run while a build of an hour goes on, and the session is never held by a task it merely watches. Its surface's STOP still reaches it where it waits. A line whose surface has gone (a browser closed while its line runs, which a browser's line survives) is anyone's to stop, since nobody else can.

A line a browser sends to the session is that browser's. A word a browser claims runs in that browser; one it did not claim the session runs, and it never opens on a different browser that does claim it. A chell's line in a claimed word still goes to a browser: its terminal's own, else the one most recently active.

One thing is never a foreground command: index movement. The global topology sweep, a feed's first-visit walk, and the visit delta are work the process cache does for itself, started by whatever asked and finished off the lane; a command that needs a topology the cache does not yet hold answers at once that the feed is indexing, and the graph follows on the feed's watch when the walk lands. The serialized lane therefore never holds a minute of page fetches, and the surfaces that share the session go on answering while a large feed indexes. This is the escape hatch background jobs would have been for the one case that needed it, without a job facility: the walk is deduplicated by the cache's in-flight map, annunciated on the prompt, and cannot be cancelled, because nothing waits on it.

=== The command path

All input enters through one prompt grammar. Input matching the chell dispatch table executes deterministically with no model involvement, at the cost of a table lookup. Input that does not match falls through to intent resolution: the kernel assembles context (current VFS position, available commands, live entity names drawn from actual platform state), consults the language model, and receives a proposed resolution. The guard then validates the proposal against the same live vocabulary: a proposed command must exist in the dispatch table, a proposed path must be resolvable, a proposed plugin must be present on the connected CUBE. Only a validated proposal is dispatched, through the identical deterministic path a directly typed command would take. The language model therefore cannot execute anything the shell could not; it can only save the user the typing.

Validation proves a proposal well formed; it cannot prove the proposal matches what the user meant, and that distinction matters most when the consequence is irreversible. Each dispatch-table entry therefore declares a mutability class: read, create, or destroy. Intent-resolved read and create proposals execute immediately with their trace displayed, keeping conversation fluid where mistakes are cheap. Intent-resolved destroy proposals do not execute: the engine echoes the exact resolved command and requires confirmation through the prompt capability, on every surface. Typed commands are never gated beyond what their builtin already does; the confirmation exists only on the probabilistic path. This is the doctrine completing itself: confirmation is the deterministic layer (the human) ratifying the model's guess before it becomes an action. The classification lives in the dispatch table, declared once where the builtin is written and merely read by the guard; expensive-but-additive operations (large plugin runs) are a recognized candidate for a fourth confirmation class once compute cost is real.

.The command path: precedence of truth
----
 input line
     │
     ▼
 [ dispatch table ] ──hit────────────────────▶ [ deterministic execution ]
     │                                                     ▲
     │ miss                                                │
     ▼                                                     │
 [ context assembly ]                                      │ validated
  cwd, command table, live vocabulary                      │ proposal
     │                                                     │
     ▼                                                     │
 [ intent compiler (LLM) ] ──proposal──▶ [ guard ] ────────┘
                                             │
                                             │ invalid
                                             ▼
                                  reject + resolution trace
----

=== The wire contract

Calypso's response envelope is the real API of the system and carries both renderings of every result: the text a terminal prints, and the typed structured model (a listing, a feed description, a job status) from which a graphical panel renders. This duality is cheap to supply because chili already separates model-producing command modules from text-producing view modules; the envelope simply stops discarding the model after rendering. The envelope also carries status codes and, for intent-resolved input, the resolution trace (what was proposed, what was validated, what ran), so that surfaces can make the model's mediation visible rather than hidden.

.The result envelope
----
 CommandEnvelope
 ┌───────────────────────────────────────────────────────────────┐
 │ status    ok | error | ...                                     │
 │ rendered  text for a terminal (ANSI permitted)                 │
 │ model?    typed result: listing, feed, job status, ...         │
 │ trace?    intent resolution record: proposed, validated, ran   │
 └────┬────────────────┬────────────────────────┬─────────────────┘
      │                │                        │
      ▼                ▼                        ▼
  terminal         graphical panel         mediation display
  prints           renders the model       shows what the model
  rendered                                 proposed vs what ran
----

The wire format is purpose-built JSON validated at the boundary. No Collection+JSON construct crosses it. Bulk byte transfer (file upload and download) is excluded from the command channel by design and handled out of band, either streamed or by direct authorized URL, keeping the WebSocket channel for commands and events.

Rendered text always carries ANSI color, and plain text is produced by stripping at the boundaries that need it. Calypso host forces color generation on (there is no TTY to auto-detect), so remote terminals render the same visual experience as the local CLI, whose auto-detection is unchanged; a thin client simply writes the received bytes to its own terminal. The pipe and redirect boundary strips ANSI from data before feeding it onward, one small utility applied in one place. This is a deliberate, documented deviation from byte-identical CLI behavior: today piped output carries escape bytes because color detection sees the TTY regardless of capture, a latent quirk rather than a behavior worth preserving, and pipes become correctly plain. Panels consuming the typed model never touch rendered text and need no stripping.

Output streams through one host-owned sink, but the sink distinguishes opaque text from semantic telemetry. Data writes are the command's actual output: they are accumulated by dispatch into the final envelope's rendered text, so pipes, redirection, and capture consume exactly what a terminal would have shown. Error writes are presented to the user and recorded as rendered error text where appropriate, but are not pipe data. Status writes are ephemeral terminal text (spinners, transient messages, notices): displayed live when a surface can display them, never accumulated, never piped. Lower layers that cannot depend on BRASA use Cumin's narrow runtime-output port; the host binds that port to the active sink at write time, so Salsa command notices follow the same destination without dissolving the sandwich layering. A pipe captures data but forwards stderr to its enclosing sink, never directly to calypso process. A calypso session host may also stream captured data and error chunks live to the command's origin surface as `output` messages; the remote renderer records which channels were streamed and suppresses only those duplicate rendered fields when the final envelope arrives. The envelope remains authoritative for completion, capture, pipes, redirects, and replay.

Progress is not a status string. Operations such as `pull`, `upload`, and `download` construct progress facts at the point that already knows the work state, and emit a structured progress event through the sink. A terminal progress bar is one rendering of that event, not the protocol itself. On the wire, opaque text remains an `output` event tagged with a correlation identifier and channel; structured progress is a distinct `progress` event with fields such as operation, phase, label, item identity, counters, unit, and status. Progress events are live-only, correlated to the command, sent to the originating surface, never accumulated, never piped, and absent from the final envelope. If a surface disconnects, progress frames may be lost; the completion record is the result envelope, and durable workflow truth remains in CUBE.

Workflow and pipeline progress follow the same doctrine but not the same producer path. Transfer and PACS retrieve progress are command-local telemetry. Workflow progress is slow process-state over a plugin-instance tree and should be derived later from CUBE snapshots (`/proc/jobs`, workflow/plugin-instance status), not replayed from daemon memory. The protocol vocabulary may reserve room for workflow progress, but calypso is not an authority for it.

Each command produces exactly one final envelope; on the wire this appears after any live output/progress events as a correlated result. A surface may send `cancel {id}` for its own foreground `execute {id}`. CALYPSO never lets a sibling cancel another surface's command. A cancellation-aware engine receives one scoped request and returns its normal final error envelope; cancellation is cooperative, so an in-flight CUBE request completes before recursive traversal stops. The first cancellation-aware operation is `du`; other foreground operations retain their existing completion behavior until they explicitly adopt the same engine seam.

Errors travel in the envelope by draining at the boundary. The stack's internal error model (the Result type plus a shared error stack) is untouched: dispatch checkpoints the error stack before a command runs and drains anything pushed above the checkpoint into the envelope's errors field afterward. A remote surface therefore receives full error detail with each result, and stale errors cannot bleed from one command into the next. One known hazard qualifies this: background work (boot-time prefetch, cache warming) pushes to and pops from the same stack outside any command boundary today, which would corrupt a drain window. Drain correctness therefore requires that background work be fenced during command execution or its stack traffic attributed, and this must be verified, not assumed, during the engine campaign.

The model slot is typed at three altitudes, each doing the only thing its layer can. cumin defines the envelope with a discriminated but untyped slot (a namespaced kind string such as `fs.listing` alongside unknown data), knowing the shape of the slot but never the payloads. chili, which owns the domain models, exports the kind-to-type map and a typed accessor so in-stack consumers get full narrowing. calypso owns the wire schemas that validate every envelope at the boundary, and those schemas are the published contract a web console programs against; external consumers read calypso's contract and never import chili. Each new model kind is therefore registered twice, once in chili's map and once in calypso's schema, and that duplication is deliberate: chili's map records what the code produces, calypso's schema records what the wire promises, and divergence between them must surface as a loud test failure rather than as silently shared drift. A single shared types package was considered and rejected for exactly this reason: it dissolves the boundary by letting internal refactors mutate the public contract implicitly.

Interactivity is a declared surface capability rather than an assumption about standard input. The host provides an input broker alongside the output sink: a prompt request, with hidden-input support, that the CLI host backs with readline (generalizing the existing question registration pattern) and calypso backs with a server-to-client request message. Surfaces declare their capabilities when they attach (hidden input, local edit, tty), and a builtin requiring an absent capability fails with a clear message rather than hanging. The edit builtin is the exemplar: the engine fetches file content, hands it to the surface's local editor (the client machine's editor for a remote CLI, an editor component in a browser), and uploads the result on save; the builtin itself knows nothing of processes or standard input.

Shell escapes follow the same surface rule. `!command` means “run this on the issuing surface,” never “run this beside the engine.” An in-process ChELL surface spawns the command locally. With `chell --remote`, CALYPSO sends a correlated `shell` request only to the originating client, which executes it with inherited terminal streams and returns `shellResult` or `shellError`; calypso does not spawn the process and its stdout is not a response channel. Consequently `!pwd` and `!ls /home/...` observe the remote ChELL client's filesystem even when CALYPSO is running at a spoke. A browser-only surface does not acquire ambient host access by attaching: it must declare a separately installed shell capability, such as the ARGUS operator companion, or fail clearly.

The prompt line and tab completion follow the same thin-client rule. The engine computes the fully themed prompt string (only it knows the context the prompt renders) and pushes it to surfaces with each result and on any context change; clients print what they receive, so prompt themes work identically on every surface. After a command, CALYPSO sends the refreshed `promptline` before the correlated `result` or `error`. WebSocket ordering therefore guarantees that a remote REPL cannot draw its next prompt from context that predates a state-changing command such as `cd`. Completion is a request/response message: the client asks on each tab press, renders candidates on reply, and discards replies that arrive after further typing, with correlation identifiers making staleness trivial to detect. The one engine-side obligation is that completion handlers answer from the local VFS cache only and never trigger a network request to CUBE; if the cache cannot answer, the engine returns what it has. This must be verified explicitly when the completer is ported, since a cold-cache tab press today may reach the network.

Pipe segments are the same pattern applied to the largest security decision in the design. Today a pipeline such as `ls | grep foo` spawns the segment as a real shell command on the local machine, which is correct for a CLI and unacceptable for a calypso session, where it would let any attached surface execute arbitrary commands on calypso host. Segment execution therefore becomes a surface capability and nothing ever spawns on calypso: the in-process CLI backs the capability with exactly the current spawn behavior, and a remote CLI backs it by running the segment on the client machine (the engine sends the segment's input down, the client pipes it through its own tools, and the engine continues the pipeline with the result, including any trailing redirect into the virtual filesystem). This is semantically stronger than daemon-side execution, since users get their own tools and privileges, and it reduces calypso's command-execution attack surface to zero. A browser surface initially lacks the capability, failing such pipelines with a clear message; a curated set of common filters implemented inside the engine can restore everyday cases later without ever touching a shell. The recognized cost is a data round trip per external segment, governed by the same backpressure and bulk-transfer rules as all other output.

=== Performance argument

The path from browser to result appears longer (browser to daemon to mise stack to CUBE) but is expected to be faster in practice for the operations that dominate perceived latency. Collection+JSON makes compound operations chatty: a listing or feed inspection decomposes into many sequential REST round trips. Today each such round trip crosses the user's connection to the server. With calypso co-located with CUBE, the chatter occurs at data-center latency and the browser pays a single round trip for the composed result. Calypso is long-lived, so cumin's caches stay warm across surfaces and reconnections, where a browser tab starts cold. Job monitoring inverts from many clients polling CUBE to one daemon watching and pushing events over the session bus. The recognized costs are that calypso is a new single point of failure, and that a Node process serving multiple sessions must be protected against one slow command blocking others; both are addressed by the session isolation model (below).

=== Cache freshness in a long-lived process

Warm caches are the performance argument's asset and calypso's sharpest correctness liability, because a cache that was merely stale for a short-lived CLI becomes permanently wrong in a process that never exits. The failure that forced this discipline was concrete: cumin's object-context factory bound a folder path to its CUBE folder object at first touch and cached the binding for the life of the process, but folder paths are not stable identities in CUBE, since deleting and re-uploading a directory assigns a new folder id. Calypso would then list the dead folder forever, rendering a freshly uploaded directory as empty, and `ls -f` could not recover because the poisoned layer sat below the listing cache that the flag invalidates.

The discipline distinguishes identity stability. Contexts keyed by stable server ids (plugins, feeds) remain cached for the process lifetime, with an eviction hook (`objContext_evict`) for the rare flow that deletes the underlying resource. Contexts keyed by paths (folders) are re-resolved on every request, trading a folder-resolution round trip per listing for the guarantee that a binding can never outlive the resource it names. The full per-cache contract inventory, including the PACS provider's settled-only bounded query cache, is recorded in link:code-audit-2026-08.adoc[code-audit-2026-08.adoc]. Directory listings themselves stay in a TTL cache with explicit invalidation: mutations invalidate the listings they touch, and operations that remove or replace a whole tree (`rm -r`, a directory re-upload) invalidate the target and every cached listing beneath it, so a re-upload cannot inherit still-fresh listings from the deleted tree. Finally, a listing whose folder cannot be resolved reports "No such file or directory" instead of rendering as an empty directory, keeping resolution failures observable rather than silently absorbing them, in the same spirit as the envelope error-drain rules above.

=== Placement and enabling work in mise

CALYPSO enters mise as a fifth workspace package (`packages/calypso`, published as `@fnndsc/calypso`). The determining consideration is that every seam CALYPSO depends on is internal to mise: separating the chell engine from its readline surface, threading structured models through the response path, and revising cumin's session state. Placing calypso in a separate repository would recreate precisely the cross-repository coordination cost the monorepo was created to eliminate. A future web console, by contrast, is a consumer of the published wire contract and may reasonably live elsewhere.

At design time, three pieces of enabling work were identified. First, the engine and surface split in chell: the dispatch, session, and VFS modules become a hostable core with two hosts, the existing CLI REPL and calypso. Second, the structured response path: command results retain their typed models alongside rendered text through dispatch. These first two are in practice one campaign, because they meet at the same seam. Chell was text-only not because the data was absent (chili's command modules already produced typed models, flattened only in its view modules) but because output left the system as direct console writes to process stdout. A hosted engine cannot print to stdout at all, so hostability forced every builtin's output through a returned envelope or an injected output sink; once output flowed through a sink, carrying the typed model alongside the rendered text was a small additional step, and the view layer ceased to be a destructive one. Third, session scoping in cumin: module-level singletons (context, connection, configuration, and the error stack) assume one user per process and one configuration tree on disk. The shipped tier-1 solution makes calypso process the identity boundary; a future network-facing multi-user service remains responsible for credential custody and process lifecycle.

=== Construction order

The design decomposes into three stages, each independently useful and each deliverable without the next. The first stage is the hostable chell engine together with the structured response envelope, a single campaign of internal refactoring (as argued above, the two share one seam) that benefits the CLI on its own. The cumin singleton work is deliberately excluded from this stage and from the next: it blocks only the multi-user milestone, the single-operator daemon tolerates the singletons because one daemon process is one session, and process-per-session behind a small multiplexer remains available as a bridge that requires no cumin surgery at all. The deep fix is scheduled when multi-user service is actually in reach, informed by operational experience with calypso rather than speculation. The second stage is the remote terminal: calypso hosting the engine over WebSocket, giving a browser an embedded ChRIS shell with zero language-model involvement. The third stage is the intent layer proper, arriving as an interception on the same prompt. This ordering is not merely pragmatic; the safety doctrine requires the deterministic path to exist and be solid before the probabilistic layer is added, since the latter is defined entirely in terms of validation against the former.

The language model behind the intent layer is replaceable, contained, and dieted. Replaceable: the intent compiler consumes a narrow provider adapter (assembled context and input go in, a proposed command line comes out), with concrete providers selected by daemon configuration; which provider ships first is a config entry, not an architectural decision, and a locally hosted model is an explicit later option for deployments where data must not leave the machine. Contained: the provider key lives in daemon configuration only, under the same custody as the attach token, touched by exactly one module so the exposure is auditable the way the chrisapi seam is. Dieted: prompts may carry vocabulary (command names and help text, the working directory, plugin names, feed titles) and the user's typed input verbatim, but never file contents or anything read from CUBE storage. On a medical platform even path names can carry patient identifiers, so the vocabulary policy is subject to a redaction review before any deployment with real patient-derived naming, and intent-enabled sessions state at boot which provider is configured.

== Results

=== Stage one: the hostable chell engine (complete)

The first stage — the hostable chell engine and the structured response envelope — is complete and merged. Its work decomposed into the result envelope and output sink (defined in cumin and chell), incremental conversion of the builtins, envelope-consuming pipes and redirection, and finally the engine facade with the REPL reduced to a thin host. On top of that seam three further pieces landed: interactivity became a declared surface capability (a host advertises what interaction it can offer, and a builtin fails clearly when the surface cannot provide it, rather than assuming standard input); the error stack became async-context aware, so each command drains exactly its own errors into the envelope while fire-and-forget background work is fenced in its own scope; and the progress-reporting builtins were assessed, with their conversion to structured status-channel events deferred to the stage where a non-terminal surface exists to consume them.

The stage met its three exit criteria. The unit suites pass across all four packages (cumin, salsa, chili, chell), verified by exit code. The live end-to-end exemplars pass against a real CUBE: connection and PACS visibility, a filesystem round trip, a feed running dcm2niix with the NIfTI output verified, and a PACS query/retrieve with state restored afterward. And observable command-line behavior is byte-identical to the pre-campaign shell, with the single documented exception that piping and redirection now strip ANSI so downstream tools receive plain text.

=== Stage two: the CALYPSO session daemon (complete)

The second stage met its daemon-topology criteria with two independent proofs. The live exemplar (`05_calypsoDaemon`) starts a real `chell --daemon` against the configured CUBE, attaches over the CALYPSO WebSocket contract, executes through the daemon-hosted engine, performs a safe CUBE filesystem round trip, inspects `/proc`, restarts the daemon, and verifies that working directory and identity rehydrate from the existing context files. Separately, ordinary CI launches an actual headless browser against a loopback web page and a local `CalypsoDaemon` backed by a stub engine; the page performs the attach handshake, runs `version`, and treats the structured result as the embedding proof. Keeping browser compatibility independent of live CUBE credentials proves browser embeddability without coupling Chromium to external-service testing or claiming a web console exists. The live smoke completed in tens of seconds; no formal browser-vs-daemon latency benchmark has been recorded yet.

Tier-1 supervision subsequently made berths identity-keyed, allowing several isolated daemon processes per OS account while retaining one engine and one CUBE identity per process. Daemon startup now shares the interactive cache-warming path, reports Plugins, Pipelines, Feeds, Public, Jobs, and final engine readiness, and publishes its listening berth only after that blocking warm-up has settled. Index movement that no command announces travels on the prompt context's `procWarmup` segment and is pushed by the once-a-second heartbeat while present: the global sweep (`loaded`/`total`, `sweeping`), one feed's first-visit topology load (`feed`: id, loaded, total — a 20k-node feed is a page walk of a minute or more, and the prompt and the ARGUS JOBS readout count it down), and roster arrivals (`arrived`: feeds created since, or shared to this identity, held for thirty seconds after the roster walk that found them). Each blocking warm-up operation receives three complete attempts with brief 250 ms / 500 ms backoff; a terminal-hosted daemon asks whether to continue degraded or exit after exhaustion, while a non-interactive daemon retains availability-first degraded startup rather than waiting for input. See link:session-supervisor.adoc[session-supervisor.adoc].

The same exit-gate run exposed a live PACS cleanup edge in `04_pacsQR`. The test series existed before the run, so the exemplar deleted it to prove admin cleanup and then attempted to restore it. A restore retrieve stalled even though a fresh retrieve repaired the same series immediately afterward. Scheduled coverage no longer enters that recovery path: it queries and verifies a pre-existing series without retrieving or deleting it, and deletes a series folder only when the exemplar proved the series absent before its own retrieve. Cleanup for that test-created folder is registered before retrieval so partial materialization after a timeout is also owned and removed. The architectural conclusion is unchanged: workflow truth is CUBE state, not daemon memory; the testing conclusion is stronger — routine validation must never make pre-existing PACS materialization depend on recovery succeeding.

Later work (the intent layer and network-facing multi-machine service) will record here, as it is built: latency measurements comparing compound operations through calypso against the same operations issued directly from a browser client; verification results from exemplar-style end-to-end scenarios driven through the assistant interface; and observed behavior of the guard layer (proposals accepted, proposals rejected, and why).

== Discussion

The transposition dissolves two standing problems rather than merely avoiding them. The first is cumin's filesystem assumption. Its configuration and context persistence were written for a Node process with a home directory, and the browser storage provider intended to bridge this was never completed beyond a non-persisting stub. In the present architecture no mise code runs in a browser at all; the filesystem assumption lands on a calypso session's local disk, where it is conventional, and the browser stub becomes deletable. The second is the Collection+JSON burden on user interface developers. The web console programs against the CALYPSO wire contract and stands four layers removed from `chrisapi`; the adapter seam that relieved the CLI stack now relieves the web equally.

The transposition also introduces obligations. Calypso is a single point of failure and a tenancy question, discussed above as known debt. The wire contract, once a web console exists against it, becomes a versioned public interface and must be treated with corresponding care. The versioning discipline exists from the first daemon release: the contract version is the calypso package's major version, carried by the client in the attach handshake and refused by calypso on mismatch, with no silent degradation. Within a major, changes are additive only (new message types, new optional fields, new model kinds), and boundary validation tolerates unknown additions from a newer minor while rejecting structural violations; anything that removes or re-types forces a major. The boundary schemas are the single source of truth, with the human-readable protocol description checked against them so that contract drift between code and documentation is structurally impossible. And the disposition of the design study should be stated plainly: the ARGUS repository is frozen, not mined. Its CALYPSO implementation is entangled with the scaffolding the study required (the in-memory filesystem, the manifest engine, the simulated federation) and follows idioms local to that codebase. The durable assets (the kernel discipline, the protocol shape, the session bus semantics, the response envelope) are ported into mise by re-implementation under mise conventions, with the study's documents cited as the record of why.

=== The federated corollary

The architecture acquires its strongest justification in federated operation, where a user sits centrally but execution and data are confined to a spoke's trust boundary. Two properties carry the weight. First, because the engine and the surface are separable, the engine can live where the data must stay: a CALYPSO daemon runs at the spoke, inside the boundary, embedding an engine connected to the spoke's CUBE, and the central user attaches a thin surface over a tunnel. A Python application running on spoke data is then observable centrally (its process status, its logs, its materialized outputs) while the engine doing the observing never leaves the spoke. This inverts calypso's status: for a single operator calypso is a convenience over an in-process engine, but for federation it is necessary, since the engine must be co-located with data precisely because the user cannot be. Second, the wire contract is a typed egress choke point. What crosses the boundary is not a screen share or a file mount but envelopes whose contents are classified by kind and channel, so a spoke can attach egress policy mechanically: job status and log tails may leave, file contents may not, or only after review. This is the trusted-research-environment pattern (code goes in, only vetted outputs come out) with the vetting surface provided by the protocol itself rather than bolted on. Two honest limits temper the claim. Classification by model kind is necessary but not sufficient: the rendered text of a command such as cat is the file content, so an enclave profile must apply content-aware policy to the data channel itself, not only to kind tags. And the genuinely hard parts of trusted research environments are disclosure control and governance process, which no wire format solves; the protocol supplies the interception surface, not the policy.

Three earlier decisions are conditional under this posture and are recorded as such. Client-side pipe execution becomes an egress path (piping a spoke file through a central client's grep ships the content across the boundary) and inverts at a spoke to in-engine filters or denial; the capability model absorbs this as a deployment-time grant rather than a redesign. Out-of-band bulk download becomes denied or vetted rather than direct. Scrollback replay and transcripts are egress like any other output. The design therefore needs one added concept, cheap now and expensive to retrofit: an egress class alongside the mutability class, with deployment profiles (open for a single-operator local daemon, enclave for a spoke daemon) selecting which model kinds, channels, and capabilities may cross. Profiles are stage-four-adjacent work, but naming the concept now prevents earlier stages from hard-coding the open posture.

A companion essay (docs/surfaces.adoc) develops a consequence of the wire contract for user interface construction: with server-state synchronization deleted from the client, framework choice is demoted from architecture to taste, and all surfaces, framework-based or bespoke, attach to calypso as siblings.

One conceptual note completes the picture. In the study's vocabulary, ARGUS named the watchman role: distributed state made legible to an operator. That role is not discarded with the prototype; it migrates, and federation makes it literal. A ChRIS web console whose panels are live renderings of CUBE state, organized around an embedded CALYPSO terminal, is the ARGUS role re-founded on a real platform; a central operator attached to one session per spoke daemon is the watchman with an eye on each spoke.

== Conclusion

CALYPSO on mise is the design study collapsed onto production infrastructure with its doctrine intact. CUBE holds the truth. The mise stack makes the truth navigable and operable deterministically. The shipped session host makes that deterministic layer available to sibling surfaces; the future intent layer will make it conversational without ever being allowed to outrank it. The terminal, on the command line today and inside the web console next, is the single interaction grammar over all of it. The first two construction stages are complete; the intent layer remains separately gated design and implementation work.

[appendix]
== Engineering plan (sketch)

The construction order of the architecture section decomposed into four stages of engineering work. This appendix records the original sketch; stages zero through two are shipped, while the later stages remain a plan of intent, not a schedule.

The zeroth stage is contract definition. The result envelope (typed model, rendered text, status) is defined in cumin alongside the existing Result type before any builtin is touched, since it is the type everything later converges on. The wire protocol (execute, complete, output events, session events, correlation identifiers) is sketched on paper from the design study's protocol shape but not implemented. This stage changes no behavior.

.Stage zero: contract sketch
----
 client to daemon                     daemon to client
 ┌───────────────────────┐           ┌────────────────────────────────┐
 │ attach   {session}    │           │ result   {id, envelopes}       │
 │ execute  {id, line}   │           │ complete {id, candidates}      │
 │ complete {id, prefix} │           │ output   {envelope}       push │
 └───────────────────────┘           │ session  {surface, envelope}   │
                                     └────────────────────────────────┘
----

The first stage is the chell campaign: engine and surface split together with the output sink, as one effort. An output sink interface is introduced and owned by the REPL host, so dispatch ceases to be the place where text reaches the screen. Builtins are converted incrementally from printing to returning envelopes, with a compatibility shim allowing converted and unconverted builtins to coexist so the work lands as a series of small changes. Pipes and redirection are re-plumbed to consume the envelope's rendered text, replacing the existing capture machinery rather than running alongside it. Finally the engine facade is extracted (creation and wiring, line execution returning envelopes, completion), and the REPL shrinks to a thin host around it. The exit criteria are a green unit suite, green live exemplars, and byte-identical CLI behavior. This stage has the largest regression surface of the plan, and the ongoing test coverage effort is its safety net.

.Stage one: output path, before and after
----
 before:
   stdin ─▶ REPL ─▶ dispatch ─▶ builtins ─▶ console.log ─▶ stdout
                    (engine fused to surface; rendering is destructive)

 after:
                            line               ┌────────────────┐
   stdin ─▶ [ REPL host ] ───────────────────▶ │  chell engine  │
                 │                             │  dispatch      │
              sink to         envelopes        │  builtins      │
              stdout   ◀────────────────────── │  (no stdout)   │
                                               └────────────────┘
----

The second stage is calypso, arriving as the fifth workspace package. A WebSocket server hosts a single engine instance, speaking the stage-zero protocol with schema validation at the boundary, and a session bus broadcasts every envelope to all attached surfaces. Authentication in this version is the CLI's model unchanged: one process, one operator, one CUBE session, singletons intact. The attach perimeter is the proven local-daemon model: calypso binds the loopback interface only, generates a random token at startup (printed, and written to a user-readable file for same-user auto-discovery), and requires the token in every attach handshake, compared in constant time. Remote access is deliberately an operator concern (an SSH tunnel or TLS-terminating proxy in front, with the token still gating attach inside it). Ordinarily CUBE credentials never cross the wire: calypso establishes its CUBE session from its own configuration exactly as the CLI does, and surfaces authenticate to calypso, not to CUBE. The deliberate exception is an explicit `sudo` command: its originating surface returns an administrator username and hidden password through the correlated prompt channel so calypso can mint a temporary CUBE client for that nested command. Those values are neither broadcast nor persisted, and the scoped client is discarded when the command settles. Therefore remote `sudo` requires a transport the operator trusts for credentials (such as an SSH tunnel or TLS proxy), not merely possession of an attach token. Per-user authentication and session ownership belong to the multi-user stage and are not half-built here. The sibling-surfaces claim is proven cheaply by giving chell a remote flag so the same REPL can drive either an in-process engine or a calypso session; two shells attached to one daemon, each seeing the other's commands, validates the topology. A minimal browser terminal page serves as an embedding smoke test; a real web console is out of scope. Exit criteria are exemplars passing through calypso and a demonstrated crash test.

.Stage two: one daemon, sibling surfaces
----
   chell --remote                    browser terminal page
         \                                  /
          \  websocket                     /  websocket
           ▼                              ▼
   ┌────────────────────────────────────────────┐
   │               CALYPSO daemon               │
   │   session bus: every envelope broadcast    │
   │   to all attached surfaces                 │
   │              [ chell engine ]              │
   └─────────────────────┬──────────────────────┘
                         │
                         ▼
                     [ CUBE ]
----

The third stage is the intent layer itself. Interception happens on the same prompt: dispatch-table matches execute deterministically as before, and only misses fall through to resolution. The components are built in dependency order: context assembly from live state (working directory, command table and help text, plugin names, resolvable paths), the intent compiler behind a provider adapter, and the guard that validates every proposal against the same live vocabulary before dispatch. The resolution trace enters the envelope so surfaces can render the mediation. Testing transposes the design study's oracle method: scenarios phrased in natural language, assertions made against CUBE state through the exemplars harness. This stage begins populating the Results section of this document.

.Stage three: interception on the same prompt
----
 "ls /proc/jobs"           "which of my jobs failed overnight?"
         \                            /
          ▼                          ▼
        [ one prompt, one grammar ]
                     │
                     ▼
        [ dispatch table ] ──hit──▶ execute       (stages one and two)
                     │
                     │ miss                       (stage three adds)
                     ▼
        [ compiler ] ──▶ [ guard ] ──valid──▶ dispatch
                             │
                             └──invalid──▶ reject + trace
----

The fourth stage, deliberately deferred, is multi-user service: process-per-session behind a small multiplexer as the bridge, and the cumin session-scoping refactor only when a real multi-user deployment is in sight, together with the design of per-session credential custody.

.Stage four: multi-user bridge, process per session
----
   user A surfaces           user B surfaces
       │     │                   │     │
       └──┬──┘                   └──┬──┘
          │  websocket              │  websocket
          ▼                         ▼
   ┌────────────────────────────────────────┐
   │              multiplexer               │
   └──────┬─────────────────────────┬───────┘
          │                         │
          ▼                         ▼
   [ daemon process A ]      [ daemon process B ]
    engine + singletons       engine + singletons
    scoped per session        scoped per session
          \                         /
           ▼                       ▼
                   [ CUBE ]
----

Two decisions recorded as open at the time of writing are now resolved: the envelope type lives in cumin, and the remote client lives inside chell. The language-model provider for the third stage remains open (and unblocking, since an adapter interface is required regardless). Remote interrupt semantics — what a cancel keystroke does to a remote foreground command — also remain undesigned and require their own follow-up.

[bibliography]
== References

* [[[chris]]] The ChRIS Project. Boston Children's Hospital / FNNDSC. https://chrisproject.org
* [[[cjson]]] M. Amundsen. Collection+JSON media type. http://amundsen.com/media-types/collection/
* [[[chrisapi]]] `@fnndsc/chrisapi`: JavaScript client for the ChRIS API. https://github.com/FNNDSC/fnndsc
* [[[mise]]] MISE: monorepo for the ChELL stack (cumin, salsa, chili, chell). https://github.com/HARBOR-Medical-AI/mise
* [[[argus]]] ARGUS: ATLAS Resource Guided User System. Design study, FNNDSC, 2025-2026. Frozen repository; see in particular its architectural principles, agentic safety foundations, and oracle testing methodology documents.
* [[[ias]]] ChRIS Intent-Action Service proposal. https://github.com/FNNDSC/intent-server
* [[[jupyter]]] Messaging in Jupyter: the Jupyter kernel wire protocol. https://jupyter-client.readthedocs.io/en/latest/messaging.html
* [[[monad]]] J. Snover. The Monad Manifesto. Microsoft, 2002.
* [[[plan9]]] R. Pike, D. Presotto, K. Thompson, H. Trickey, P. Winterbottom. The Use of Name Spaces in Plan 9. Operating Systems Review, 1993.
* [[[vscode]]] Visual Studio Code Remote Development and the VS Code Server. https://code.visualstudio.com/docs/remote/vscode-server
