Named shells that stay alive across turns — the same session Mori drives is the one you can type into. Local, Docker, or SSH, with output kept bounded so a runaway command can't blow memory or context.
A terminal session is a long-lived shell that Mori keeps open across turns, keyed by a name you choose. cd into a directory, export an env var, start a build — all of it persists in that session until you close it. Reuse the same name and you pick up exactly where you left off; open a new name and you get a clean shell.
Five tools drive them: terminal_open starts (or reattaches to) a named session, terminal_run executes a command and waits for it to finish, terminal_read reads recent output without running anything, terminal_list enumerates every open session, and terminal_close reaps one. This is a real, stateful shell — not a one-shot run_terminal_command that forgets everything the moment it returns.
Named sessions shipped in 1.3.0 SOVEREIGN.
> terminal_open { name: "build", backend: "local" }
Opened terminal session "build" [local] at /Users/you/site. Run commands in it with terminal_run.
> terminal_run { name: "build", command: "cd apps/web && npm ci" }
[build] exit 0
…
> terminal_run { name: "build", command: "npm run build" } # still in apps/web
[build] exit 0
…You choose where a session's shell actually runs when you open it. A name is pinned to its backend for the life of the session — reopening the same name on a different backend is refused rather than silently swapping shells underneath you.
zsh or bash on macOS and Linux, PowerShell on Windows. It inherits the environment Mori is running in, so your PATH and tools are already there. Commands land on your real machine.All three look identical to the model once open: the same commands, the same bounded output, the same exit codes.
When you open a docker session, Mori starts a fresh container locked down before any command runs — you don't have to remember the flags. The container is started lazily on open and reaped with docker rm -f the moment you close the session, so nothing lingers.
node:20-bookworm-slim; pass docker_image to pick another.It degrades gracefully: if the docker binary isn't on your PATH or the container fails to start, terminal_open returns a clear error and tells you to install Docker or use the local backend — it never crashes the app.
docker run -d \
--cap-drop ALL \
--security-opt no-new-privileges \
--pids-limit 256 \
--memory 2g \
-v <your workspace>:/work -w /work \
node:20-bookworm-slim sleep infinityAn ssh session opens a persistent remote shell, multiplexed through an SSH ControlMaster socket so reconnecting to the same host is fast. It is built to fail fast rather than hang:
ServerAliveInterval keeps the connection healthy, and the shared control-master is torn down when you close the session so no dangling connection is left behind.ssh binary or an unreachable host returns an error, never a crash.Give it the destination as [user@]host when you open the session.
A shell that stays open forever could flood the model's context with the output of one chatty command. Every session is built to prevent that.
terminal_run.…N lines elided… marker. The result is flagged as bounded so you know it was trimmed.terminal_read and the UI; older lines age out rather than accumulate forever.terminal_run returns what it has so far and tells Mori the command may still be running — terminal_read catches up later, or terminal_close ends it. For servers that never exit, start_dev_server is the right tool instead.Closing a session reaps its shell — and for Docker or SSH, the container or the connection too. Every open session is also reaped when you quit the app.
A persistent shell does not mean an ungoverned one. Every command sent through terminal_run passes the same command chokepoint as any other shell command Mori runs: an un-bypassable hardline floor refuses catastrophic patterns outright, and dangerous-but-legitimate commands are classified and gated according to your current autonomy level.
That means a terminal_run of rm -rf ~ is blocked no matter what, and a sudo or a git reset --hard prompts for approval under the default Guided level. How much runs without stopping to ask you is governed by the Autonomy ladder — see Autonomy for Guided / Trusted / Commander and the hardline floor.