Lookup material, grounded in the current CLI. For an ordered first run, see Get started.

Reference

Reference

The supported torio command surface and the fixed boundaries of Torio.

Running the binary

Build with go build -o torio ./cmd/torio, then put the result on your PATH — for example sudo install -m 755 torio /usr/local/bin/torio. Every command below is written as torio … and assumes that.

A freshly built binary sits in the repository root and is not on your PATH, and your shell does not search the current directory, so torio yields command not found until you install it. Running it in place as ./torio … from the repository root also works, but then every command needs the prefix.

Command surface — torio vm

Controls the torio Lima VM. torio vm init creates it; every other subcommand operates on the existing instance. The parent takes no action itself; an absent or unknown subcommand is a usage error.

CommandWhat it does
torio vm initCreate the Torio VM from the embedded pinned template, or succeed idempotently when a compatible instance already exists. Incompatible instances fail closed — there is no --force. Sizing: --cpus N, --memory SIZE, --disk SIZE (defaults 4, 8GiB, 60GiB). They apply at creation only, since init never recreates.
torio vm statusReport the Torio VM state.
torio vm startStart the VM. Idempotent; confirms a Running post-state before reporting success.
torio vm stopStop the VM. Graceful and idempotent; never uses --force, never removes the VM or its data, and requires a Stopped post-state.
torio vm bootstrapReconcile and verify the existing target for Hermes. Installs the pinned Hermes Agent when the launcher is missing; verifies operator membership in torio-projects. Idempotent on a reconciled target. Accepts --timeout and --json.
torio vm ssh -- COMMAND…Run a command inside the VM. Does not open an interactive shell, forward stdin or a TTY, migrate data, start a chat, or copy credentials.

Command surface — torio serve

Manages the loopback-only Hermes Desktop backend as a user systemd service. It accepts no secrets.

CommandWhat it does
torio serve installGenerate, validate (with systemd-analyze), and enable the backend user service. Idempotent; does not start the backend.
torio serve startStart the backend and prove loopback readiness.
torio serve stopStop the backend service.
torio serve restartRestart the backend and prove loopback readiness.
torio serve statusReport systemd state and loopback endpoint readiness.
torio serve logsShow recent, bounded, redacted, unit-scoped service logs. Accepts --lines N.

Command surface — torio brain

Manages the private Second Brain: a Markdown vault at /home/hermes/brain, versioned by a local Git repository, owned by hermes, and registered with Hermes as its own project so any session can search it. The parent takes no action itself; an absent or unknown subcommand is a usage error.

CommandWhat it does
torio brain initCreate the canonical scaffold atomically through private guest staging, make the first local commit, and register the Hermes project. Then install or refresh the global torio-brain retrieval skill so other projects can search the Brain. Idempotent for managed state; refuses to touch non-empty data it did not create. Configures no remote and pushes nothing.
torio brain statusReport state (initialized, uninitialized, or drift), the canonical path, native filesystem, ownership and mode, Git worktree state, aggregate counts, Hermes project registration, and skill state. Changes nothing.
torio brain import <host-directory>Import an existing Markdown vault through private host and guest staging, verified by checksum on the guest. Accepts --into SUBDIR to land the import as one new contained subtree, and --dry-run to preflight without transferring anything.

Output never contains note names or note content — not in success output, not in error.details. Every command reports bounded aggregate metadata only: file counts, total bytes, a manifest digest, and stable drift markers. This is the Brain's privacy boundary, not a matter of brevity.

import refuses or skips credential-shaped files, repository metadata, links, hardlinks, special files, and executables. Existing data is never overwritten — the single exception is an untouched scaffold that Torio itself created.

Sessions that were already open when init ran will not see the retrieval skill: Hermes caches a skill's prompt per backend process, so restart them.

Getting the Brain back out

Torio brings data in and does not take it out. There is no torio brain export. Copying the Brain to your host is an explicit thing you do:

limactl copy torio:/home/hermes/brain/ <host-destination>/

That is your command, not a Torio feature: nothing verifies the result, and Torio does not call it a backup.

Command surface — torio project

Attaches repositories to the guest, inspects them, and forgets them. The parent takes no action itself; an absent or unknown subcommand is a usage error.

CommandWhat it does
torio project add <name> <remote>Clone the exact remote into the derived workspace path, or verify and adopt a checkout already there; give the operator and hermes shared access; register the project with Hermes before recording it in config. --id SLUG picks an id other than <name>; --use makes it active on success.
torio project listList the registered projects. Reads config only, runs nothing on the guest, and works with the VM stopped.
torio project show <id>Report the registry entry, checkout state, and Hermes registration. Reports drift as stable markers instead of repairing it, and returns no filenames, diffs, or raw Git output.
torio project use <id>Make a registered project the active one in Hermes.
torio project remove <id>Archive the Hermes project and drop the config entry. The checkout is never deleted, and the output says where it still is.
torio project enter <id>Open an ordinary interactive terminal in the checkout with SSH agent forwarding disabled. Interactive, so it does not support --json.
torio project shell <id>Open an ephemeral operator session in the checkout with your SSH agent forwarded. Interactive, so it does not support --json.

The workspace path is not an input. It is always derived as /home/hermes/projects/<id> — never taken from you, never stored in config. Without --id, the id is <name> itself, which must be a lowercase slug.

Torio stores no Git credentials. A remote the guest cannot already read without prompting fails closed; the fix is a human granting access on the guest, outside Torio, not a retry.

add resets, cleans, and deletes nothing on the guest, so a rerun after a failure finishes the work rather than starting over.

Routine terminals and push capability are separate

Use torio project enter <id> for ordinary editing, checks, and local commits. The SSH transport disables agent forwarding and connection multiplexing, so it cannot reuse a push-capable operator connection.

The persistent Hermes backend has read access and nothing more. project shell forwards your SSH agent for exactly as long as the session lasts, and the capability leaves with you when you exit.

The session is preflighted first — the project registered, the VM bootstrap-verified, the checkout present with the registered origin and shared permissions, your local agent holding an identity to forward. Torio never test-pushes to prove any of it, and once you exit it makes no claim about what you pushed. Check the remote yourself.

The session is not bounded by --timeout; you end it.

Command surface — torio mcp

Prepares and verifies the guest boundary that a future MCP broker will use. The parent command takes no action itself; an absent or unknown subcommand is a usage error.

CommandWhat it does
torio mcp installProvision the unprivileged torio-mcp identity, its private 0700 home, the torio-mcp-clients group, and the root-owned policy directory. Then verify the resulting custody boundary. A settled rerun reports changed:false.
torio mcp statusVerify the identity, group, home, policy, Hermes-profile, and optional runtime invariants without repairing them. A never-provisioned guest is an unmet precondition; drift is a verification failure.

install accepts no secrets. It may report changed:true together with an error when a durable mutation succeeded before a later verification failed. That partial state is retained in JSON details and the human error explains the required rerun or backend restart.

Both subcommands report the grant they verified: every service in /etc/torio-mcp/policy.d/, its upstream endpoint, how many tools it allows and how many of those write. Nothing about a service is built into the CLI — a service is a policy document, and adding one means writing a second file as root. Under --json the grant is a policy object with a services array ordered by name, alongside the generation digest a running broker publishes, so a report and the process enforcing policy can be compared rather than assumed equal.

A reported write tool is a count, not a capability. The count exists because a document that marks writes must be able to say how many it grants; no released binary sends MCP traffic upstream.

This is custody preparation, not an active integration. The released CLI does not package, install, or activate the dormant broker and relay binaries. It does not perform OAuth or send MCP traffic upstream. Runtime transport and credential lifecycle remain blocked by ADR-0004 until a complete contract is accepted.

Command surface — torio version

CommandWhat it does
torio versionPrint the version, commit, build date, and Go toolchain of the binary you are running. Accepts --json.

This is the only place Torio names its own version. Nothing else in the CLI or in these docs is labelled by release, because the label would not tell you anything you could act on — and it would be wrong the moment the next one shipped.

A binary built straight from a checkout reports dev with an unknown commit; that is expected, not a fault.

Readiness and exit semantics

Global flags

Four flags are accepted by every command, before or after the subcommand. This is the whole list; an unknown flag is a usage error (exit 2), never silently ignored, and there is no global --force.

FlagWhat it does
--jsonEmit a single machine-readable JSON document on stdout, where the command has one to emit; human logs stay on stderr. --help is the one exception — it prints usage and exits 0 without an envelope.
--verboseRaise stderr diagnostics from warnings to debug. Stdout is untouched, so machine output is identical with and without it, and the extra lines are redacted like every other diagnostic.
--timeout DURATIONBound the operation. Default 30s, or default_timeout from the config file when set; an explicit flag always wins. Anything above the policy maximum 10m is rejected before any work starts.
--config PATHRead the non-secret config document from PATH instead of $XDG_CONFIG_HOME/torio/config.json. It bypasses XDG entirely, is resolved and validated rather than merely parsed, and applies to the project registry as well; a missing file or an invalid document is a usage error (exit 2).

Fixed boundaries

ThingValue
VMtorio — a Lima instance Torio creates from a pinned template and never re-images or deletes
Guest identityhermes
Profile / application state/home/hermes/.hermes (native Linux filesystem; Hermes $HERMES_HOME, not the Second Brain)
Second Brain vault/home/hermes/brain (native Linux filesystem; separate from profile)
Workspace root/home/hermes/projects (setgid torio-projects shared workspaces)
A project's checkout/home/hermes/projects/<id> — always derived from the project id, never supplied
Backend bind127.0.0.1:9119 inside the VM (never a public address)
Backend API authnon-public /api/* requires an X-Hermes-Session-Token header; /api/status is public. Headless serve surfaces no token, so the operator pins one.
Serve unit working directory/home/hermes/hermes-agent — which is why Desktop's working directory must be set explicitly

Rootful Docker for the hermes guest identity is forbidden. Bootstrap verifies hermes is not in the docker group.

Deployment

This site is served at torio.dev by Vercel, straight from the committed site/ output: a minimal vercel.json declares the output directory, and there is no build step, no framework, and no runtime dependency. Account, project, and DNS configuration live outside the repository, with the operator.

The pages under site/ are generated by scripts/build_docs.py from the Markdown sources in docs/content/. The generated files are committed, so a push to main is a deploy.