---
title: "Commands"
description: "Every command the shoal binary answers to, with its options and the help it prints."
source: https://shoal.grimvane.ai/docs/reference/commands/
---

# Commands

This page is generated from the binary. Each command below, every flag, every default, and every line of help is read out of the program at version 0.32.0 rather than written beside it, so a reference that disagrees with what runs on your machine is a build failure rather than a page nobody noticed.

> **For the VS Code extension.**
>
> The extension runs these for you. It spawns `shoal proto` and talks to it, so the commands here are what you reach for when you set the machine up, check what it found, or manage a server outside the editor.

> **For the desktop app.**
>
> The app window is `shoal desktop`. The rest of these commands are what you reach for when you set the machine up, check what it found, or manage a server that outlives a single window.

## shoal

Shoal - a local-first coding agent

Shoal is a coding agent that runs on your own hardware. It manages the  
inference server itself, keeps history stable for the server's prefix  
cache, asks before writes and shell commands, and delegates subtasks  
to fresh-context sub-agents. Cloud APIs are optional, never required.

Install once: shoal setup. Save a default model: shoal config set  
model <name>. After that, a bare shoal in any terminal is enough.

Server lifecycle: shoal up, shoal down, shoal doctor, shoal models.

Docks (where models tie up):

-   mlx: Apple Silicon via mlx\_lm.server (macOS only)
-   llamacpp: NVIDIA CUDA / Metal via llama-server (all platforms)
-   remote: an OpenAI-compatible server you run, here or on another box
-   anthropic: Anthropic's API, reached by a mooring record with your key
-   openai: OpenAI's API, reached by a mooring record with your key

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--auto-approve` | bool | - | Approve write/exec tool calls without prompting (required for -p mode to use them) |
| `--dock` | string | - | Inference dock: mlx, llamacpp, remote (auto-detected if omitted) |
| `--endpoint` | string | - | Skip server management, use this OpenAI-compatible endpoint |
| `--hitl` | bool | `true` | Ask before a decomposition plan is accepted (default: on) |
| `--index` | bool | - | Rebuild the project chart and exit |
| `--model` | string | - | Model identifier |
| `--prompt` `, -p` | string | - | Non-interactive: run single prompt and exit |
| `--resume` | string | - | Resume a previous session by ID or unique prefix |
| `--version` `, -v` | bool | - | version for shoal |

Those options except `--version` are persistent: --auto-approve, --dock, --endpoint, --hitl, --index, --model, --prompt, --resume can be given to any command below as well, not only to a bare `shoal`.

## shoal audit

What a session touched, read and produced

audit reads the row log: one append-only record per tool call, per  
agent loop and per model call, written under the data directory and  
outliving the session that wrote it.

shoal audit index every session in the log  
shoal audit --session <id> one session  
shoal audit --day 2026-09-01 one day  
shoal audit --json the same index as one JSON object  
shoal audit --store where the log lives and what it costs

It is an index, not a transcript. It says which files were read,  
written and failed, which calls were refused, what the model calls  
cost, and how much of the window was in use. It does not carry tool  
arguments or results: a row is a fact about a call, not a copy of it.

The log is on by default and shoal config set rowlog off stops it.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--day` | stringSlice | `[]` | one or more days, as 2026-09-01 |
| `--json` | bool | - | print the index as JSON |
| `--main-only` | bool | - | leave out what delegated children did |
| `--session` | string | - | one session id |
| `--store` | bool | - | print where the log lives and what it costs |

## shoal berth

Run the agent loop here for a surface on another machine

berth runs the engine on this machine and listens for one surface to  
drive it. The loop, the tools, and the workspace are here; a person  
sitting at another machine runs "shoal connect" or "shoal desktop  
\--berth" and works in this tree.

The default listen address is 127.0.0.1, which reaches nothing but  
this box. Reaching further is an address you type: --listen  
0.0.0.0:7823 offers the berth to your network.

Every connection is authenticated and every connection is TLS. The  
token lives in the secrets store under "berth\_token";  
\--mint-token generates and saves one on the way up. The certificate is  
self-signed and kept in \`~/.shoal/berth/\`, and the client pins its  
fingerprint, which this command prints. Nothing dials out and there is  
no relay: a berth behind NAT is reached through your own network.

A dropped connection does not stop the engine. Nobody attached for the  
linger period does: --linger 0 keeps it up until you stop it.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--cert-host` | stringSlice | `[]` | Extra hostnames or addresses the self-signed certificate should answer to |
| `--linger` | duration | `15m0s` | Shut down after this long with nobody attached; 0 never does |
| `--listen` | string | `127.0.0.1` | Address to listen on; a bare host takes the default port |
| `--mint-token` | bool | - | Generate a token, save it under the secret name, and print it once |
| `--tls-cert` | string | - | Use this certificate instead of the self-signed one |
| `--tls-key` | string | - | Private key for --tls-cert |
| `--token-secret` | string | `berth_token` | Name of the secrets-store entry holding the berth token |

## shoal board

The metrics board: did the window hold, and what did it cost

board renders one session's telemetry from the row log: whether the  
window held, what the tools did, what landed, and what it cost.

shoal board the last real session (eval runs skipped)  
shoal board --session <id> a named session  
shoal board --json the same board as one view-model JSON object  
shoal board --with-eval fold in rows a benchmark run tagged eval

The spine is window-first: size, peak in use, drops, then activity,  
and spend leads only where a mooring carries real prices. Rows the  
eval runner tagged source=eval are excluded by default and the board  
says how many it left out.

It reads the same log shoal audit reads. The audit is the trail and  
never filters; the board is the view and says what it excluded.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--json` | bool | - | print the view model as JSON |
| `--session` | string | - | one session id (default: the last session with a non-eval row) |
| `--with-eval` | bool | - | include rows tagged source=eval |

## shoal config

Show or set user-wide defaults (model, dock, endpoint, breakwater, trust, rowlog, memory, voyage)

config manages the user-wide defaults so shoal runs without flags.

shoal config show the current defaults  
shoal config set model <name> set the default model  
shoal config unset model clear a default

Keys: model, dock (mlx, llamacpp, remote), endpoint, breakwater, trust,  
rowlog, memory, voyage.

breakwater is open or closed, and closed is the default even when the  
key is unset. Closed means Shoal's own code reaches loopback and the  
destinations a mooring names, and refuses every other one. Open takes  
that wall down. shoal doctor prints which it is and what is named.

trust is the label a mooring gets when its own record carries none.  
Unset is the default and means unlabelled, which is the full toolset,  
exactly as before this key existed. Set it to unvetted to say you have  
not vetted the models on this machine: every unlabelled mooring then  
runs on the read-only toolset and says so when it starts. A record  
labelled trusted still gets everything.

rowlog is on or off, and on is the default even when the key is unset.  
On writes one JSON row per tool call, per agent loop and per model call  
to an append-only log under the data directory, which is what answers  
what a session touched, read and produced after the session itself is  
gone. Off writes nothing at all. shoal audit reads it.

voyage is on or quiet, and on is the default even when the key is  
unset. On, the status line above the composer narrates the turn as a  
sea passage, phase by phase, each phase entered because its event  
happened. Quiet gives the plain status words back.

memory is the directory the user-scope memory store lives in. Unset is  
the default and means the memory folder under ~/.shoal, so a machine  
that never sets it changes nothing. Set it to move the memory store  
and only the memory store, for example to a folder another tool also  
reads. SHOAL\_HOME is the wrong tool for that: it moves the whole Shoal  
home, secrets included, and while it is set it wins over this key. An  
absolute path, or one starting with ~. shoal memory shows which rule  
is deciding the path.

Precedence at launch: CLI flag, then the project's .shoal/config.json,  
then these defaults.

unset clears one key. To clear the launch defaults together, so the  
next launch is a first run: shoal reset-defaults. To walk the  
first-run flow without clearing anything: shoal first-run.

### shoal config set <key> <value>

Set a default

### shoal config unset <key>

Clear a default

## shoal connect <host\[:port\]>

Drive a Shoal engine running on another machine

connect speaks the protocol seam to a berth: an engine running  
somewhere else, with its own workspace and its own filesystem. Events  
come out on stdout as JSON lines and ops go in on stdin, exactly as  
"shoal proto" does, so a client written for one drives the other.

The berth prints a certificate fingerprint when it starts. Pass it  
with --fingerprint. A berth on this same machine needs none: its  
certificate is on this disk and connect reads it.

The token comes from the environment (SHOAL\_BERTH\_TOKEN) or from  
the secrets store, never from a flag, because a flag value is visible  
in the process list.

Closing stdin leaves the remote loop running. To stop it, send a  
shutdown op.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--ca` | string | - | Verify the berth against this CA instead of pinning a fingerprint |
| `--fingerprint` | string | - | The berth's certificate fingerprint, as it printed it |
| `--token-secret` | string | `berth_token` | Name of the secrets-store entry holding the berth token |

## shoal desktop

Open Shoal as a desktop app window

desktop runs the engine and opens the Shoal web client in an  
app-mode window on a loopback port. Closing the window exits Shoal;  
the inference server follows the usual ownership rules (a server  
started by shoal up persists, a session-owned one stops).

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--berth` | string | - | Drive an engine running at this berth instead of one in this process |
| `--ca` | string | - | Verify the berth against this CA instead of pinning a fingerprint |
| `--fingerprint` | string | - | The berth's certificate fingerprint, as it printed it |
| `--no-open` | bool | - | Serve only: print the URL instead of opening a window |
| `--token-secret` | string | `berth_token` | Name of the secrets-store entry holding the berth token |

## shoal doctor

Check platform, dependencies, and running services

Detect the host platform, verify the dependencies shoal needs  
(including the llama-server security floor), and report which inference  
servers are answering locally.

An inference server whose owning session is gone is stopped and  
reported, so the memory it holds goes back to the machine. A server  
started by shoal up records no owner and is never touched.

## shoal down

Stop persistent inference servers

Stop every shoal-managed inference server and clear stale pid  
records. A server shoal did not start is reported but left running.

## shoal exec \[prompt\]

Run one prompt without a terminal and exit with a code

exec runs one prompt on this machine and exits. There is no terminal,  
nothing listens on a socket, and no ops are read after launch: stdin is  
at most the prompt.

The prompt comes from the argument, from --file, or from stdin, and  
exactly one of the three. With --workflow the same text becomes the  
workflow's argument instead.

Output modes:  
text the final assistant text on stdout, notices on stderr  
json one result document: final text, stop reason, spend, files  
touched, and every action the ladder refused  
stream every seam event as JSON lines on stdout, as it happens.  
This is the mode to script against: the vocabulary is the  
engine's own protocol, not a second schema

Exit codes:  
0 the turn completed and nothing was refused  
1 the flags or the prompt could not be read  
2 the engine did not start  
3 the turn ended in an error  
4 the turn spent its whole tool call budget  
5 the turn finished and the ladder refused at least one call  
6 the run was interrupted  
7 the input gate answered the prompt without starting a turn

Approvals cannot ask anybody here, so they resolve from a rung of the  
autonomy ladder. The rungs are named after Shoal's own tool classes:

read runs from the read rung  
reads a file or searches the tree  
delegate runs from the read rung  
hands work to a sub-agent, whose own calls come back through  
this same table  
plan runs from the write rung  
accepts a decomposition plan  
write runs from the write rung  
writes to a file  
exec runs from the exec rung  
runs a shell command  
hook runs from --unsafe-approve-all  
calls an outside process a hook started  
tool runs from --unsafe-approve-all  
carries no classification, so nothing here knows what it  
does

The floor is read, which is the default. A write-classified call at the  
floor is refused and reported, never dropped in silence. Whether a call  
needs approving at all is the engine's own decision (internal/  
permissions), the same one every other client asks, so a shell command  
Shoal already treats as read-only runs at the floor too.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--autonomy` | string | `read` | Autonomy rung: read, write, exec |
| `--file` `, -f` | string | - | Read the prompt from a file |
| `--output` `, -o` | string | `text` | Output mode: text, json, stream |
| `--session` | string | - | Continue a stored session by ID or unique prefix |
| `--unsafe-approve-all` | bool | - | Approve every call, including shell commands and hook tools |
| `--workflow` | string | - | Run a named workflow instead of a prompt; the prompt becomes its argument |

## shoal first-run

Replay the first-run flow without clearing anything

first-run walks the flow a new machine gets, on a machine that is  
already set up.

Nothing is cleared. The model list opens on the default this machine  
already has, so pressing Enter through it leaves everything as it was;  
picking a different model saves that one, exactly as the first launch  
would have.

This is the flow to reach when you want to SEE the first run. To make  
the next launch a first run for real, which does clear what is saved:

shoal reset-defaults

On the windowed surfaces the same two acts are /first-run and  
/reset-defaults, and the first is also the "Set up this machine again"  
row at the bottom of the model picker.

## shoal memory

Show where memory lives and what it holds

memory shows the two stores a session in this directory would read:  
the project scope at .shoal/memory in the repository, and the user  
scope, which is the memory folder under ~/.shoal unless the memory key  
moves it (shoal config set memory <dir>) or SHOAL\_HOME moves the whole  
Shoal home. It says which rule decided the user path, how many records  
each scope holds, which slugs the project scope overrides, and which  
records carry a format newer than this build reads.

Read-only. Records are files, one fact each, in a published format, so  
either folder can be opened in any editor.

shoal memory this directory's stores  
shoal memory discover other harnesses' stores on this machine

### shoal memory discover

Find other harnesses' memory stores on this machine, read-only

discover looks where other agent harnesses keep memory and reports  
what is there and where: Claude Code's per-project stores, Gemini  
CLI's memory file, the Goose and Windsurf memory folders, the folders  
OpenCode, Factory and Codex keep, and the neutral .agents location,  
under the home directory and the current directory. Every location it  
looked at is printed, found or not.

It reads names, counts, and the names of frontmatter keys. It never  
reads a record's text, never shows one to a model, and never copies  
one into Shoal's own store. Import is not built: it needs opt-in per  
source and a review of every record before recall can see it, because  
a folder of files written by another tool is untrusted text, and  
recall is the path that carries text into a prompt without a person  
reading it first.

\--json prints the same sweep as one document.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--json` | bool | - | machine-readable output |

## shoal models

List known models and recommend one for this hardware

Show what is on disk, the model registry with per-dock identifiers,  
a hardware-aware recommendation, and what each model on disk says about  
itself. A model already on disk that fits the memory budget is recommended  
over a registry download. The sizing is a  
heuristic (weights against a memory budget with KV headroom); any GGUF path  
or MLX id outside the registry still works with --model.

Provenance is reported in three states and never two. What a GGUF header  
says about its own name, publisher and licence is claimed: the header  
travels with the file, so nothing here checked it. The path, the size and  
the sha256 are verified, because this machine read them. Anything absent  
is not stated, which is silence and not a finding.

\--hash reads every model on disk and records its digest, so a later  
listing can report it as verified. Nothing is fetched and nothing is  
compared: the number is printed for you to compare against whatever the  
publisher published.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--hash` | bool | - | read every model on disk and record its sha256 (slow: it reads every byte) |
| `--json` | bool | - | machine-readable output |

## shoal proto

Speak the protocol seam over stdio (JSON lines)

proto runs the engine as a child process for GUI clients: protocol  
events stream out on stdout, one JSON object per line, and ops arrive  
on stdin the same way. stdout is pure protocol; startup prints and  
diagnostics go to stderr. Closing stdin shuts the engine down cleanly.

Approvals assume an interactive approver on the other end: tool calls  
that need approval emit approval\_requested events and wait for a  
resolve\_approval op. Pass --auto-approve to skip approvals entirely.

## shoal reset-defaults

Clear the saved launch defaults, so the next launch is a first run

reset-defaults clears the user-wide launch defaults in one act:  
model, dock and endpoint. The next launch then resolves no model and  
meets the first-run condition, which is the terminal wizard on a  
terminal and the launch portal on the windowed surfaces.

What it does not touch, and why:

breakwater, trust policy, not convenience. Unset already means  
the wall is up, and clearing trust would widen  
what an unlabelled mooring may do.  
mooring records a record is something you wrote down. Forget  
removes one, by name, from the mooring form or  
/moor on a windowed surface. A record carrying  
the main purpose supplies the launch on its  
own, so this command names it when one still  
does rather than deleting it for you.  
the project config .shoal/config.json belongs to the repository,  
not to this machine. It is reported, never  
edited.

To clear one key instead of all of them, shoal config unset <key>. To  
walk the first-run flow without clearing anything, shoal first-run.

## shoal secret

Manage the secrets hooks authenticate with

secret manages the values a hook needs to authenticate, kept  
outside every project and readable only by you.

shoal secret set <name> store a value, read from a prompt or stdin  
shoal secret list list names and fingerprints, never values  
shoal secret rm <name> remove one

Reference a secret from a hook's env in hooks.json as ${secret:NAME}.  
It resolves when the hook process is spawned, into that process's  
environment, and never into a prompt, a tool result, or a stored  
session. A value that comes back out in a hook's output is replaced  
with \[redacted:NAME\] before it reaches the model.

### shoal secret list

List secret names and fingerprints

### shoal secret rm <name>

Remove a secret

### shoal secret set <name>

Store a secret, reading the value from a prompt or stdin

set reads the value from a prompt, or from stdin when it is piped.

The value is never taken as an argument: arguments are visible in  
shell history and in the process list.

shoal secret set LINEAR\_TOKEN  
echo $TOKEN | shoal secret set LINEAR\_TOKEN

## shoal sessions

List and manage sessions

## shoal setup

Install shoal as a program (uninstall with --uninstall)

setup installs the running binary as a per-user program:

-   copies it to the user programs directory
-   puts shoal on PATH, so any new terminal runs it by name
-   registers it with the OS (Start Menu, Apps & features on Windows)

Re-running setup from a newer binary upgrades the install in place.  
\--uninstall reverses everything; session data and config are kept.

When no default model is saved yet, setup asks for one as part of  
installing, so a bare shoal starts on every surface afterwards. The  
same default can always be set or changed by hand:

shoal config set model <name>

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--uninstall` | bool | - | Remove the installed program (session data and config are kept) |

## shoal up

Start a persistent inference server

Start the inference server detached, so it outlives this command.  
The model loads once; later shoal runs find the server healthy on the port  
and use it instead of spawning their own. Stop it with shoal down.

The server binds to 127.0.0.1 only, never exposed to the network.

| Flag | Type | Default | What it does |
| --- | --- | --- | --- |
| `--sleep-idle` | int | `0` | unload weights after N idle seconds, keep listening (llama.cpp dock only, 0 = off) |

This reference matches Shoal 0.32.0.
