--- title: Nori CLI description: The open-source agentic coding terminal — one harness for any ACP agent. status: draft sidebar: order: 2 --- Nori is a harness, not an agent. The `nori` binary (npm: `nori-ai-cli`) is one terminal for Claude Code, Codex, Gemini, or any agent that speaks the Agent Client Protocol — with sessions you can resume and fork, skills, MCP, hooks, sandboxed approvals, and a hand-built TUI. ```sh npm install -g nori-ai-cli nori ``` ## Sections - [start/](/cli/start/) — install, first run, connecting your agent's auth - [guides/](/cli/guides/) — agents, sessions, approvals, MCP & hooks, cloud - [reference/](/cli/reference/) — commands, `config.toml`, slash commands, keybindings - [develop/](/cli/develop/) — architecture, crate map, contributing ## Open source The CLI began as a fork of OpenAI Codex; the agent engine has since been removed and every agent runs as an external ACP subprocess. Source, issues, and releases: [github.com/tilework-tech/nori-cli](https://github.com/tilework-tech/nori-cli). --- --- title: The Agent Client Protocol description: ACP is the host-agent contract that lets Nori run any compatible coding agent. status: verified sidebar: order: 2 --- The **Agent Client Protocol (ACP)** is the contract between a host (like Nori) and a coding agent. It's what makes "any agent" possible. ## The wire protocol ACP is JSON-RPC spoken over standard input/output between the host and an agent subprocess. The host sends requests — initialize, create or load a session, prompt a turn — and the agent streams back session updates (message chunks, tool calls, edits) as the turn runs. Permission flows the other way: the agent asks the host before privileged actions, and Nori answers according to your [approval settings](/cli/guides/approvals/). Nori builds on the official ACP Rust crate (`agent-client-protocol`). ## The extension boundary Because the boundary is a protocol — not a Rust API — an agent can be written in any language and distributed any way (a local binary, `npx`, `bunx`, `pipx`, or `uvx`) and still plug into Nori. There is deliberately **no in-process plugin system**; the extension point is ACP itself. ## Nori's role Nori is an **ACP host**: it launches agents, translates the wire protocol to and from its internal session model, and renders the result. The host side lives in a dedicated library crate, `nori-acp-host` — agent registry, subprocess spawning, the JSON-RPC connection, and permission plumbing — with a scriptable `mock-acp-agent` for conformance testing. ## Next steps - [Agents & harnesses](/cli/concepts/agents-and-harnesses/) — why Nori runs every agent out of process - [Agents guide](/cli/guides/agents/) — register your own ACP agent in `config.toml` - [Architecture](/cli/develop/architecture/) — where the ACP host sits in the three-layer workspace --- --- title: Agents & harnesses description: Nori is the harness — an ACP host that runs your coding agent as a separate process. status: verified sidebar: order: 1 --- The Nori CLI is a **harness, not an agent**. It has no model and no coding engine of its own — it runs your chosen coding agent and gives it a terminal, durable sessions, approvals, and tool plumbing. ## Host and agent - **Nori (the host, the harness)** — the TUI, session transcripts, approvals, MCP forwarding, and the loop that drives a turn. - **The agent** — Claude Code, Codex, or any agent you register. It runs as a **separate subprocess** and talks to Nori over a protocol. The key idea: Nori never reimplements an agent — it hosts one. ## The out-of-process boundary Each agent keeps its own authentication, its own slash commands, and its own tools. Running it as a subprocess means Nori doesn't have to absorb any of that — it forwards. Slash commands the agent advertises over the protocol appear in the composer alongside Nori's own, and the MCP servers from Nori's `config.toml` are handed to the agent when a session starts. Switching agents — `/agent` in the TUI, or `-a ` at launch — swaps the whole subprocess, cleanly. ## The protocol The host-agent contract is the [Agent Client Protocol (ACP)](/cli/concepts/acp/). Anything that speaks ACP can be the agent — which is exactly why "any agent" is a real promise and not a marketing line. ## Next steps - [Agent Client Protocol](/cli/concepts/acp/) — what actually travels between host and agent - [Agents guide](/cli/guides/agents/) — the built-in agents, and how to register your own - [MCP & hooks](/cli/guides/mcp-and-hooks/) — the tool plumbing Nori forwards to whichever agent is running --- --- title: Sessions, history & transcripts description: Where Nori keeps session state on disk, and the documented JSONL transcript format. status: verified sidebar: order: 3 --- A Nori session is a conversation with an agent plus everything that happened in it — messages, tool calls, patches, and approvals. Nori records it all on the host side, independent of whatever the agent stores for itself. ## On-disk layout Everything lives under the Nori home directory: `$NORI_HOME` if set, otherwise `~/.nori/cli`. Transcripts are organized by project: ```text ~/.nori/cli/transcripts/by-project/{project-id}/ ├── project.json # project metadata └── sessions/ └── {session-id}.jsonl # one file per session ``` Message history — every prompt you've typed, recallable from the composer in any session — is a separate global file, `~/.nori/cli/history.jsonl`. Transcripts are what make sessions durable: `nori resume` (or `/resume` in the TUI) brings one back, and `/fork`, `/compact`, and `/undo` rewind and reshape it — see the [sessions guide](/cli/guides/sessions/). ## The transcript format Each session file is JSONL: one JSON object per line, each carrying a timestamp, a schema version (currently `2`), and one of seven entry types — `session_meta`, `user`, `assistant`, `tool_call`, `tool_result`, `patch_apply`, and `client_event`. The format is documented as a stable contract, so external tools — analysis scripts, other hosts, other ACP implementations — can read and write Nori transcripts without depending on Nori's internals. :::note The full schema, field-by-field, lives in the repo: [`docs/reference/transcript-format.md`](https://github.com/tilework-tech/nori-cli/blob/main/docs/reference/transcript-format.md). ::: ## Next steps - [Sessions guide](/cli/guides/sessions/) — resume, fork, compact, and undo in practice - [Architecture](/cli/develop/architecture/) — where transcripts sit in the `nori-harness` runtime layer --- --- title: Develop description: How the CLI is built, and how to contribute. status: draft sidebar: order: 4 --- - [architecture](/cli/develop/architecture/) — three layers, the ecosystem crates, fork heritage Exhaustive API docs belong on docs.rs as the Layer-0 crates publish; the session transcript format is a stable third-party contract documented in the repo (docs/reference/transcript-format.md). --- --- title: Architecture description: A harness, not an agent — three layers, strict downward dependencies. status: draft sidebar: order: 1 --- Nori CLI is a harness: it never implements an agent. Every agent is an external subprocess speaking the Agent Client Protocol, and the workspace is organized so that rule is structural, not aspirational. ``` ┌ layer 2 · frontends ────────────────────────────┐ │ nori-cli (the binary) nori-tui (terminal) │ └───────────────────┬─────────────────────────────┘ ┌ layer 1 · runtime ▼─────────────────────────────┐ │ nori-harness (sessions, transcripts, undo, │ │ hooks) nori-config (~/.nori/cli) │ └───────────────────┬─────────────────────────────┘ ┌ layer 0 · leaves ▼─────────────────────────────┐ │ nori-acp-host nori-protocol │ │ mock-acp-agent (conformance testing) │ └─────────────────────────────────────────────────┘ ``` Rules that hold the shape: dependencies point strictly downward; the TUI never talks to `nori-acp-host` directly (everything flows through harness events); nothing below layer 2 knows a terminal exists; no cargo feature may move a responsibility between crates. ## The ecosystem crates Layer 0 is being groomed for crates.io: - **nori-acp-host** — the host side of ACP as a standalone library: agent registry, subprocess spawning, JSON-RPC connection, permission plumbing. - **nori-protocol** — the event vocabulary on top of the ACP schema. - **mock-acp-agent** — a scriptable agent for conformance-testing hosts and agents. The session transcript format is a stable, documented contract for third-party tools (JSONL, one file per session under `~/.nori/cli/transcripts/`). ## Fork heritage The workspace began as a fork of OpenAI Codex. Crates prefixed `codex-*` are inherited (sandboxing, login, protocol vocabulary, utilities) and get renamed to `nori-*` as they're adopted; upstream syncing has ended, so inherited crates shrink or disappear over time. --- --- title: Guides description: Agents, sessions, approvals, MCP, hooks, and cloud. status: draft sidebar: order: 2 --- - [agents](/cli/guides/agents/) — built-ins and bring-your-own ACP agents - [sessions](/cli/guides/sessions/) — resume, fork, compact, undo - [approvals & sandboxing](/cli/guides/approvals/) — the three safety presets - [MCP & hooks](/cli/guides/mcp-and-hooks/) — servers and lifecycle scripts - [cloud](/cli/guides/cloud/) — the TUI against your org's fleet --- --- title: Agents description: Run the built-in agents or register any ACP agent of your own. status: draft sidebar: order: 1 --- Every agent runs as an external ACP subprocess — Nori is the harness around it. Pick with `/agent` in the TUI or `-a ` at launch. ## Built in | Slug | Provider | Context window | Runs via | |---|---|---|---| | `claude-code` (default) | Anthropic | 1M | the Claude Code ACP adapter | | `codex` | OpenAI | 258K | the Codex ACP adapter | | `gemini` | Google | 1M | Gemini CLI's native ACP mode | Adapters launch through `bunx` when bun is on your PATH, otherwise `npx`. ## Bring your own Register any ACP-speaking agent in `~/.nori/cli/config.toml`: ```toml [[agents]] name = "My Agent" slug = "my-agent" [agents.distribution.npx] package = "my-acp-agent" args = ["--acp"] ``` Distribution variants: `local` (a command on your machine), `npx`, `bunx`, `pipx`, or `uvx`. Optional keys: `context_window_size`, `auth_hint`, `transcript_base_dir`. A custom slug that matches a built-in replaces it. Capabilities are agent-driven: Nori enables `/compact`, `/fork`, session listing, and `/config` per agent based on what the agent advertises in its ACP handshake. --- --- title: Approvals & sandboxing description: Three presets from read-only to full access, enforced by OS-level sandboxes. status: draft sidebar: order: 3 --- `/approvals` switches between three presets: | Preset | Asks before | Can write | |---|---|---| | **Read Only** | anything that isn't reading | nothing | | **Agent** (default) | actions outside the workspace | the workspace | | **Agent (full access)** | nothing | everything | Sandboxing is enforced by the operating system, not by prompt discipline: Landlock + seccomp on Linux, Seatbelt on macOS, restricted tokens on Windows. `nori sandbox ` runs any command inside the same sandbox the agent gets — useful for testing what the agent can and can't touch. `--dangerously-bypass-approvals-and-sandbox` (alias `--yolo`) disables both. The name is the warning. Config keys: `approval_policy` (`always` / `on-request` / `never`) and `sandbox_mode` (`read-only` / `workspace-write` / `danger-full-access`) in `config.toml`. --- --- title: Cloud description: Run the same TUI against a cloud session in your org's fleet. status: draft sidebar: order: 5 badge: { text: beta, variant: caution } --- `nori cloud` runs the familiar TUI, but the agent lives on a cloud VM in your org's [Nori Sessions](/sessions/) fleet — same conversation model, remote compute, resumable from Slack or the web afterwards. ```sh nori cloud ``` Requirements: - [handroll](/sessions/handroll/) installed and logged in (`nori-handroll login`) — the CLI delegates all broker auth and transport to it - your org's broker URL, either from your handroll login or set explicitly: ```toml [cloud] broker_url = "https://.norisessions.com" ``` In cloud mode the agent choice is pinned to the cloud session — `--agent` selects agents for local runs only. --- --- title: MCP & hooks description: Connect MCP servers and run your own scripts at session lifecycle boundaries. status: draft sidebar: order: 4 --- ## MCP servers Nori is an MCP client. Configure servers under `[mcp_servers.]` in `config.toml`; manage connections in the TUI with `/mcp`. ```toml [mcp_servers.github] command = "npx" args = ["-y", "@modelcontextprotocol/server-github"] [mcp_servers.internal] url = "https://mcp.example.com" bearer_token_env_var = "INTERNAL_MCP_TOKEN" ``` Both stdio and streamable-HTTP transports are supported, with per-server `enabled`, startup/tool timeouts, and `enabled_tools`/`disabled_tools` filters. ## Hooks `[hooks]` runs your scripts at eight lifecycle boundaries — `session_start`, `session_end`, `pre_user_prompt`, `post_user_prompt`, `pre_tool_call`, `post_tool_call`, `pre_agent_response`, `post_agent_response` — each with an `async_`-prefixed fire-and-forget variant. ```toml [hooks] session_start = ["~/.nori/hooks/announce.sh"] async_post_tool_call = ["~/.nori/hooks/log-tools.py"] ``` The interpreter follows the extension (`.sh` → bash, `.py` → python3, `.js` → node); `~` expands; synchronous hooks time out after `script_timeout` (default 30 s). --- --- title: Sessions description: Resume, fork, compact, and undo — sessions are durable and rewindable. status: draft sidebar: order: 2 --- Every session is recorded as a JSONL transcript under `~/.nori/cli/transcripts/`, independent of the agent's own storage. On exit, Nori prints the exact command to pick up where you left off. ## Resume ```sh nori resume # picker, sessions for the current directory nori resume --last # newest session nori resume # a specific session nori resume --all # across all directories ``` In the TUI: `/resume`, or `/resume-viewonly` for a read-only transcript. ## Rewind and recover - `/fork` — rewind the conversation to an earlier message and branch from it - `/undo` — ask Nori to undo a turn's changes (Esc also offers undo) - `/compact` — summarize the conversation to stay under the context limit - `/new` — fresh chat without restarting ## Transcript format The on-disk transcript schema is a documented, stable contract for third-party tools — see [architecture](/cli/develop/architecture/). --- --- title: Reference description: Commands, config, slash commands, keybindings. status: draft sidebar: order: 3 --- - [commands](/cli/reference/commands/) — the nori binary surface - [config.toml](/cli/reference/config/) — every configuration section - [slash commands](/cli/reference/slash-commands/) — generated from source - [keybindings](/cli/reference/keybindings/) — generated from source --- --- title: Commands description: The nori binary's subcommands and flags. status: draft sidebar: order: 1 --- *This page will be generated from the CLI's own definitions (the clap command tree renders directly to markdown). Until the regen script lands, the surface is summarized by hand:* ``` nori [OPTIONS] [PROMPT] interactive TUI (the default) nori resume [--last|] resume a recorded session (--all: any directory) nori cloud TUI against a cloud session (see guides/cloud) nori login | logout agent login helpers nori sandbox run a command inside the agent's sandbox nori skillsets ... passthrough to nori-skillsets nori completions shell completions ``` Common flags: `-a/--agent `, `-p/--profile`, `-i/--image`, `-C/--cd `, `--add-dir`, `-c key=value` (config override), `--dangerously-bypass-approvals-and-sandbox` (`--yolo`). --- --- title: config.toml description: Every configuration section of ~/.nori/cli/config.toml. status: draft sidebar: order: 2 --- Lives at `~/.nori/cli/config.toml` (`$NORI_HOME/config.toml`). Everything is optional; these are the sections and their load-bearing keys. The full key-by-key reference will be generated from the config schema. | Section | Governs | Highlights | |---|---|---| | top level | agent & safety | `agent` (default `claude-code`), `approval_policy` (`on-request`), `sandbox_mode` (`workspace-write`), `history_persistence` | | `[tui]` | the terminal UI | notifications, `notify_after_idle`, `vim_mode`, `auto_worktree` (`automatic`/`ask`/`off`), `file_manager`, `custom_working_messages` | | `[tui.hotkeys]` | keybindings | see [keybindings](/cli/reference/keybindings/) | | `[tui.footer_segments]` / `[tui.footer_layout]` | status bar | 11 toggleable segments, per-corner placement | | `[mcp_servers.]` | MCP servers | stdio or HTTP transport, timeouts, tool filters — see [MCP & hooks](/cli/guides/mcp-and-hooks/) | | `[hooks]` | lifecycle scripts | 8 sync + 8 async hook points | | `[[agents]]` | custom ACP agents | see [agents guide](/cli/guides/agents/) | | `[default_models]` | per-agent model | e.g. `claude-code = "haiku"` | | `[cloud]` | cloud sessions | `broker_url` | Command-line overrides win over the file: `-c key=value`. --- --- title: Keybindings description: Default hotkeys and the config keys that rebind them. status: verified editUrl: https://github.com/tilework-tech/nori-docs/blob/main/scripts/regen-cli-reference.mjs sidebar: order: 4 --- Rebind any action under `[tui.hotkeys]` in config.toml using `[modifier+]key` strings; `"none"` unbinds. | Action | Default | Config key | |---|---|---| | Open Transcript | `ctrl+t` | `open_transcript` | | Open Editor | `ctrl+g` | `open_editor` | | Move Backward Char | `ctrl+b` | `move_backward_char` | | Move Forward Char | `ctrl+f` | `move_forward_char` | | Move to Line Start | `ctrl+a` | `move_beginning_of_line` | | Move to Line End | `ctrl+e` | `move_end_of_line` | | Move Backward Word | `alt+b` | `move_backward_word` | | Move Forward Word | `alt+f` | `move_forward_word` | | Delete Backward Char | `ctrl+h` | `delete_backward_char` | | Delete Forward Char | `ctrl+d` | `delete_forward_char` | | Delete Backward Word | `ctrl+w` | `delete_backward_word` | | Kill to Line End | `ctrl+k` | `kill_to_end_of_line` | | Kill to Line Start | `ctrl+u` | `kill_to_beginning_of_line` | | Yank | `ctrl+y` | `yank` | | History Search | `ctrl+r` | `history_search` | | Toggle Plan Drawer | `ctrl+o` | `toggle_plan_drawer` | --- --- title: Slash commands description: Every in-TUI command, in the order the popup shows them. status: verified editUrl: https://github.com/tilework-tech/nori-docs/blob/main/scripts/regen-cli-reference.mjs sidebar: order: 3 --- "During task" marks commands you can run while the agent is working. | Command | Description | During task | |---|---|---| | `/agent` | switch between available ACP agents | — | | `/model` | choose what model and reasoning effort to use | — | | `/config` | configure ACP agent settings (if exposed by the agent) | — | | `/approvals` | choose what Nori can do without approval | — | | `/settings` | configure Nori CLI settings (theme, hotkeys, layout, …) | — | | `/goal` | set or view the goal for a long-running task | yes | | `/new` | start a new chat during a conversation | — | | `/resume` | resume a previous session | — | | `/resume-viewonly` | view a previous session transcript (read-only) | — | | `/close` | close (release) the current session and start a fresh chat | — | | `/init` | create an AGENTS.md file with instructions for Nori | — | | `/compact` | summarize conversation to prevent hitting the context limit | — | | `/undo` | ask Nori to undo a turn | — | | `/browse` | open a file manager to browse and edit files | yes | | `/diff` | show git diff (including untracked files) | yes | | `/mention` | mention a file | yes | | `/status` | show current session configuration and context window usage | yes | | `/memory` | show the contents of all active instruction files | yes | | `/first-prompt` | show the first prompt from this session | yes | | `/mcp` | manage MCP server connections | — | | `/login` | log in to the current agent | — | | `/logout` | show logout instructions | — | | `/quit` | exit Nori | yes | | `/exit` | exit Nori | yes | | `/switch-skillset` | switch between available skillsets | — | | `/fork` | rewind conversation to a previous message | — | | `/browser` | open a Chrome browser the agent can control via CDP | — | --- --- title: Start description: Install the CLI and run your first agent. status: draft sidebar: order: 1 --- - [install](/cli/start/install/) — npm install, first run, provider auth --- --- title: Install description: Install the Nori CLI and run your first agent session. status: draft sidebar: order: 1 --- ```sh npm install -g nori-ai-cli nori ``` macOS, Linux, and Android (Termux), on x64 and arm64. Prebuilt binaries are also attached to [GitHub releases](https://github.com/tilework-tech/nori-cli/releases). ## First run Bare `nori` opens the TUI. The default agent is Claude Code; switch any time with `/agent` or launch with `nori -a codex` / `nori -a gemini`. Nori reuses your agents' existing logins — authenticate once with each provider's own CLI: | Agent | Authenticate with | Or set | |---|---|---| | Claude Code | `npx @anthropic-ai/claude-code` then `/login` | `ANTHROPIC_API_KEY` | | Codex | `/agent` to codex inside Nori, then `/login` | `OPENAI_API_KEY` | | Gemini | `npx @google/gemini-cli` then `/auth` | `GOOGLE_API_KEY` | ## Where things live Everything Nori writes goes under `~/.nori/cli/` (override with `NORI_HOME`): ``` ~/.nori/cli/ ├── config.toml # your configuration ├── transcripts/ # session transcripts (JSONL, resumable) ├── history.jsonl # prompt history └── log/ # logs ``` --- --- title: Nori description: Take any team of AI coding agents cloud-native — any harness, any token provider, any integrations, any compute. Fully-managed agents in Slack, or bring your own multi-agent cloud. template: splash tableOfContents: false hero: title: Take your agent team cloud-native. tagline: Any harness. Any token provider. Any integrations. Any compute. Run fully-managed agents from Slack — or bring your own multi-agent cloud, on your terms. actions: - text: Quickstart link: /start/quickstart/ icon: right-arrow variant: primary - text: What is Nori link: /start/what-is-nori/ variant: minimal --- import { Card, CardGrid } from '@astrojs/starlight/components'; import HomeTerminal from '../../components/HomeTerminal.astro'; import HomeMarquee from '../../components/HomeMarquee.astro'; ## One platform, two ways in Managed cloud runtime for AI coding agents. Start an agent in a secure sandbox from Slack, hand it a task, and get results back in the thread. **Product.** [Explore Sessions →](/sessions/) The local terminal proxy that fronts any interactive agent and connects it to the cloud. Attach, detach, hand off. **Product.** [Explore Handroll →](/sessions/handroll/) One CLI, every provider. Switch between Claude, Codex, Gemini, or any ACP-compatible agent from the same native terminal. **Open source.** [Explore the CLI →](/cli/) Install and manage packaged agent configurations from an open registry. One skillset, translated to every agent's format. **Open source.** [Explore Skillsets →](/skillsets/) ## Run your --- --- title: FAQ description: Common questions about Nori — the products, the open-source core, providers, and compute. --- ## Is Nori open source? Partly, by design. The **CLI** and **Skillsets** client are open source. **Sessions** and **Handroll** are the managed product. This "open-core" split is deliberate: the open source lets you run agents yourself; the product runs them for you. ## Which agents / harnesses does Nori support? Claude Code, Codex, and Gemini ship as built-ins in the CLI, and anything that speaks the [Agent Client Protocol](/cli/concepts/agents-and-harnesses/) can be registered as a custom agent. "Any harness" is the goal, not a slogan. ## Do I bring my own model API keys? Yes. You bring your own token per provider (Claude, OpenAI, Gemini, …). Nori orchestrates the agent; the model relationship stays yours. ## Where do managed agents actually run? On a secure cloud VM from a pluggable backend (Sprites or Modal today), selected per fleet. The compute layer is designed to be swappable — see [Core concepts](/start/concepts/). ## How is this different from using one agent's cloud offering? Single-vendor clouds lock you to one harness, one provider, one runtime. Nori is the customizable stack: any harness, any token, any integrations, any compute — managed for you, or composed by you. ## Something's missing or wrong in these docs. That's a bug. Open an issue in the [GitHub org](https://github.com/tilework-tech), or use the "Edit page" link in the footer. --- --- title: For AI agents description: How AI coding agents work with Nori — skills, the CLI's MCP client, and machine-readable reference. --- Nori is built agent-first, and these docs are meant to be legible to AI coding agents as well as people. ## Public skills Two general-purpose skills are maintained alongside these docs (in the site repo's `skills/` directory) and distributed through [Skillsets](/skillsets/): **`nori-info`** (what Nori is, with canonical pointers) and **`nori-docs`** (how to fetch this site as raw markdown). They are pointers, not copies — the pages here stay canonical. ## MCP with the CLI The [Nori CLI](/cli/) is an **MCP client**: you connect [Model Context Protocol](/cli/guides/mcp-and-hooks/) servers and it forwards their tools to whatever agent you're running. It is not itself a general-purpose MCP server you point other tools at — see [MCP and hooks](/cli/guides/mcp-and-hooks/). ## Fetching these docs as markdown Every page has a raw-markdown twin: append `.md` to its path, dropping any trailing slash (`/cli/start/install/` → `/cli/start/install.md`). Two index files cover the whole site: [/llms.txt](/llms.txt) (curated map with one-line descriptions) and [/llms-full.txt](/llms-full.txt) (the entire corpus in one file). ## Machine-readable reference Where a page documents CLI help, keybindings, or supported agents, it is **generated** from the source repo and stamped with provenance (`source: @`); the reference groups carry a **Generated** badge in the sidebar. Never edit those pages by hand — regeneration overwrites them, and a drift gate diffs them in CI. --- --- title: Nori Sessions description: Managed cloud sessions for AI coding agents — a warm fleet of VMs, reachable from Slack, Discord, the web, or your terminal. status: draft sidebar: order: 1 --- Sessions keeps a warm fleet of cloud VMs, each ready to run your coding agent (Claude Code, Codex, Gemini, or a custom agent). Mention the bot in Slack, open the web dashboard, or hand off from your terminal — the platform claims a machine, distributes short-lived credentials, and tracks the session through its whole lifecycle. Your org gets a single-tenant broker at `.norisessions.com`. ## Sections - [start/](/sessions/start/) — sign up, connect a surface, run a first session - [guides/](/sessions/guides/) — Slack, the web dashboard, integrations, triggers - [reference/](/sessions/reference/) — session lifecycle, configuration, limits - [handroll/](/sessions/handroll/) — the terminal client: install, usage, cloud handoff New here? The [quickstart](/sessions/start/quickstart/) goes from signup to a working cloud agent session in one sitting. --- --- title: Checkpoints & resume description: How session state is captured so work can be resumed after the machine is gone. status: verified sidebar: order: 4 --- Sessions are ephemeral, but their work doesn't have to be. **Checkpoints** capture session state durably so a run can outlive the machine it ran on. ## What a checkpoint captures After every completed agent turn, the platform snapshots the session workspace — the working tree, including git state and untracked files, minus build artifacts like `node_modules` — into a compressed archive in durable storage. Retention and size caps are in the [limits reference](/sessions/reference/limits/). ## Resume Replying in a session's thread resumes it through the first tier that applies: if the machine is still claimed, the session re-attaches in place with full working state; if the machine is gone, the last consistent checkpoint restores onto a fresh machine. In Slack, `!resume` opens a picker of recent sessions. The full contract — and its boundary: a session is resumable whenever a checkpoint exists and it wasn't explicitly released — is in the [lifecycle reference](/sessions/reference/lifecycle/). :::note Checkpoints move sessions between *cloud* machines. Moving a session between your terminal and the cloud is Handroll's job, and is currently limited — see [hand-off & resume](/sessions/handroll/handoff-and-resume/). ::: ## Next steps - [Session lifecycle reference](/sessions/reference/lifecycle/) — resume tiers, timeouts, failure modes. - [Limits](/sessions/reference/limits/) — checkpoint retention and size caps. --- --- title: Fleets, brokers & compute description: How the broker schedules sessions onto a pool of cloud machines across pluggable compute backends — and why provider is not the same as harness. status: verified sidebar: order: 2 --- Three words describe where a session runs: **fleet**, **broker**, and **compute provider**. Getting these straight makes the rest of Sessions legible. ## Fleet A **fleet** is your org's pool of ephemeral session machines. It has settings you control — pool size, idle and inactivity timeouts, the default agent and model, the default personality, and how credentials are distributed. See the [configuration reference](/sessions/reference/config/). ## Broker The **broker** is the orchestrator. It keeps the warm pool, claims a machine when a session starts, provisions it, and runs the Slack / Discord surfaces and the HTTP API. You don't operate the broker directly; you configure your fleet and start sessions, and the broker does the scheduling. Each org gets its own single-tenant broker at `.norisessions.com`. ## Compute provider (not the same as harness) Two independent axes are easy to confuse — keep them apart: - **Compute provider** — *where the VM runs*. The backend is pluggable behind one contract, selected per fleet: **Sprites** (Fly) is the default, and **Modal** is the alternative. A "sprite" is one Fly VM — it is *a* backend, not a synonym for "session machine." - **Harness** — *what runs inside the VM*. The coding agent: Claude Code, Codex, Gemini, or a custom agent. Chosen per session. So "any compute" and "any harness" are two separate promises. You can run Claude Code on Modal, or Codex on Sprites — the axes don't constrain each other. :::note Which backend a fleet uses is a fleet-level choice, not something you set per run — and switching it drains and rebuilds the pool. See the [configuration reference](/sessions/reference/config/). ::: ## Next steps - [What is a session](/sessions/concepts/what-is-a-session/) — the lifecycle a machine moves through. - [Harnesses & personalities](/sessions/concepts/harnesses-and-personalities/) — the other axis: what runs inside the VM. - [Configuration reference](/sessions/reference/config/) — every fleet-level knob. --- --- title: Harnesses & personalities description: A harness is the coding agent that runs inside a session; a personality is the skillset applied to it at provision time. status: verified sidebar: order: 3 --- Two more concepts shape how a session behaves: the **harness** and the **personality**. ## Harness The **harness** is the coding agent running inside the session VM — Claude Code, Codex, Gemini, or a custom agent. Your fleet sets a default harness and model; a session can switch mid-thread with the `!provider` and `!model` commands (see the [Slack guide](/sessions/guides/slack/)). The harness is orthogonal to the [compute provider](/sessions/concepts/fleets-and-providers/): choosing Claude Code does not decide whether you run on Sprites or Modal. ## Personality (skillset) A **personality** is the [skillset](/skillsets/) applied to a session when it is provisioned — the prompts, rules, and configuration that shape how the agent works. Your fleet has a default personality; individual sessions can override it (name a skillset when starting the session, or switch mid-thread with `!skillsets switch`), and [triggers](/sessions/guides/triggers/) can set their own. Personalities are where Sessions and the open-source [Skillsets](/skillsets/) project meet: the same skillset format that improves a local agent also configures a managed session. ## Next steps - [Integrations & credentials](/sessions/guides/integrations/) — connect the agent providers each harness needs. - [Slack guide](/sessions/guides/slack/) — switching harness, model, and skillset from a thread. - [Skillsets](/skillsets/) — the open-source project behind personalities. --- --- title: What is a session description: A session is one agent run bound to a cloud VM, tracked by the broker through a lifecycle from warm pool to release. status: verified sidebar: order: 1 --- A **session** is a single agent run bound to a cloud machine. When you start one, the broker hands your chosen agent a fresh, isolated VM, connects your integrations, and streams the work back to you — usually a Slack or Discord thread. ## Lifecycle The broker keeps a **warm pool** of machines so sessions start fast. Each machine moves through a lifecycle: ``` bootstrapping → ready → claimed (active ⇄ idle) → dead ``` 1. **Ready** — a provisioned VM waits in the warm pool. 2. **Claimed** — a request (from Slack, Discord, the web dashboard, or a [trigger](/sessions/guides/triggers/)) claims a ready VM; your org setup and the selected [personality](/sessions/concepts/harnesses-and-personalities/) are applied. 3. **Active / idle** — the agent works (`active`); between turns the claim sits `idle`. Idle timeouts eventually wind the session down and reclaim the machine. 4. **Dead** — ending a session (for example replying `!done` in the thread) releases the machine, and the pool is replenished. Exact state names, the timeouts that drive them, and the resume contract are in the [lifecycle reference](/sessions/reference/lifecycle/). ## What a session gives the agent - An isolated cloud VM scoped to your org's [fleet](/sessions/concepts/fleets-and-providers/). - Your selected **harness** (Claude Code, Codex, Gemini, or a custom agent) and model. - Your connected [integrations](/sessions/guides/integrations/) and short-lived credentials. - Durable [checkpoints](/sessions/concepts/checkpoints-and-resume/) so work survives the machine it ran on. The point of a session is that none of this lives on your laptop — the agent runs in the cloud, on your terms, and reports back. ## Next steps - [Fleets, brokers & compute](/sessions/concepts/fleets-and-providers/) — where sessions run and who schedules them. - [Session lifecycle reference](/sessions/reference/lifecycle/) — every state, timeout, and failure mode. - [Quickstart](/sessions/start/quickstart/) — from signup to a working session. --- --- title: Guides description: Task-shaped feature docs for Sessions. status: draft sidebar: order: 2 --- - [Slack](/sessions/guides/slack/) — mentions, commands, access control - [web dashboard](/sessions/guides/web-dashboard/) — chat, sessions, fleet, setup - [integrations & credentials](/sessions/guides/integrations/) — providers, GitHub, secrets - [triggers](/sessions/guides/triggers/) — cron, reminders, webhooks --- --- title: Integrations & credentials description: Connect agent providers, GitHub, Slack, Google Workspace, MCP servers, and custom secrets — the platform handles rotation and distribution. status: draft sidebar: order: 3 --- You configure credentials once in the dashboard; the platform validates them, distributes short-lived tokens to session machines, and rotates them while sessions run. Long-lived refresh tokens never leave the broker. ## Agent providers Each provider accepts an API key **or** an OAuth login — configuring one clears the other: | Provider | API key | OAuth | |---|---|---| | Claude | `ANTHROPIC_API_KEY` | yes — takes precedence; tokens refresh every 45 minutes | | Codex | `OPENAI_API_KEY` | yes — tokens refresh every 6 hours | | Gemini | `GEMINI_API_KEY` | yes | Additional agents from the registry use the same schema-driven API-key flow. ## GitHub Two options; the personal access token wins if both are configured: - **GitHub App** — one-click install; you choose all repos or a selection. - **Personal access token** — paste and go. Commit attribution is a policy you pick: the first person to mention the bot in a thread becomes the commit author, later participants become `Co-authored-by` trailers; or attribute everything to Nori, or to the agent. ## Everything else - **Slack / Discord** — chat surfaces; see the [Slack guide](/sessions/guides/slack/). - **Google Workspace** — OAuth-only; per-user scopes distributed to sessions. - **MCP servers** — OAuth-managed connections available inside sessions. - **Custom env vars** — arbitrary key/value secrets, with an admin-only flag to hide values from members. - **Managed CLIs** — curated presets (AWS: access key + secret, region defaults to `us-east-1`). --- --- title: Slack description: Drive cloud agent sessions from Slack threads — mentions, commands, and access control. status: draft sidebar: order: 1 --- Mention the bot in a channel to start a session. It replies in a thread, and every plain reply in that thread continues the same session — no re-mention needed. The first word after the mention can name a skillset to start with. While the agent works, a progress message updates on a 10-second cadence. Files you attach are fetched by the agent inside the session (size caps in [limits](/sessions/reference/limits/)). ## Commands Slack reserves `/`, so Nori commands use `!`. In a session thread: | Command | What it does | |---|---| | `!help` | list commands | | `!status` | session status card | | `!shell` / `!ide` / `!chat` / `!urls` | links into the running session | | `!interrupt ` | interrupt the current turn with a new instruction | | `!thoughts` | show the agent's current reasoning | | `!restart` | restart the agent on the same machine | | `!skillsets [pick\|list\|info\|switch ]` | switch skillset mid-thread | | `!model [name\|pick]` · `!provider [id\|pick]` | switch model or provider | | `!resume` | pick from your resumable sessions, started on any surface | | `!catchup` | replay the thread's history | | `!admin` | request admin escalation (single-use link, expires in 5 minutes) | | `!done` / `!release` | end the session and release the machine | Fleet-wide, outside a session thread: `!fleet-status`, `!free-oldest`. Switching providers with `!provider` moves the conversation to a fresh machine: history carries over as a transcript bridge, working files do not. ## Ending vs idling `!done` is the only action that makes a thread human-only — after it, plain replies are ignored until a new mention. Every other way a session winds down (idle timeout, restart, transport drop) keeps reply-to-resume working: your next thread reply revives the session, with a catch-up digest if it advanced elsewhere. ## Access control Three modes govern who can drive the bot (set in Fleet Setup): | Mode | Behavior | |---|---| | `open` | anyone in the workspace can start and drive sessions | | `guarded` | org members trusted; other users' thread messages are queued as context, not executed | | `strict` | only allowlisted emails and admins; others are blocked | A separate knob controls the agent's own reach into Slack, from `channel-only` (default: proxied, scoped to the thread's channel) up to `direct-token` (raw token in the session; internal environments only). ## Install The Integrations checklist installs the app with least-privilege scopes. DM support, file upload, and file download are optional scope groups you can toggle off before installing. Both socket mode and HTTP mode are supported. --- --- title: Triggers description: Start sessions on a schedule, a reminder, or an inbound webhook. status: draft sidebar: order: 4 --- Triggers start sessions without a human in the loop: - **Cron** — recurring scheduled runs (org default timezone is configurable) - **Reminders** — one-shot future runs - **Webhooks** — inbound HTTP events start or continue a session Triggered sessions behave like any other: they show up in the sessions list, post to their configured surface, and follow the same lifecycle and checkpoint contract. Admin approval gates apply to trigger creation. --- --- title: Web dashboard description: Chat, session history, fleet health, and org configuration at .norisessions.com. status: draft sidebar: order: 2 --- Your org's dashboard lives at `.norisessions.com`. Chat is the landing page; everything else hangs off the sidebar. Roles: owner, admin, member. | Screen | What you do there | |---|---| | **Chat** | talk to an agent in the browser — same fleet as Slack. Past sessions from any surface (Slack, web, CLI, triggers) open read-only or resume live into a fresh machine | | **Sessions** | list every session with transcripts; resumable sessions are badged | | **Fleet** | live pool health and per-machine lifecycle, with acquire/restart/destroy controls (10-second refresh) | | **Fleet Setup** | fleet size, lifecycle timeouts, default provider/model/skillset, workspace repos, Slack access mode | | **Integrations** | the onboarding checklist: agent providers, Slack, Discord, GitHub, Google Workspace, MCP servers, custom env vars | | **Triggers** | scheduled and event-driven sessions — cron, reminders, webhooks | | **Skillsets / Skills / Build / Publish** | the skill registry surfaces | Two endpoints are public and need no login: `GET /api/health` and `GET /api/fleet/status`. Credential changes distribute to running sessions automatically — you don't restart anything after rotating a key. --- --- title: Handroll description: The terminal client for Sessions. status: draft sidebar: order: 4 --- - [usage](/sessions/handroll/usage/) — install, the Ctrl-B overlay, cloud handoff and resume --- --- title: Handroll configuration description: The on-disk config files, the environment variables, and the fixed prefix key. status: verified sidebar: order: 3 --- Handroll reads two JSON files and a handful of environment variables. This page lists them all. ## Config files ### `~/.nori-config.json` — auth Shared with the [Skillsets](/skillsets/) client — `nori-handroll login` and `npx nori-skillsets login` write the same file. It holds the `auth` section: refresh token, short-lived ID token and its expiry, and your `organizations`. You normally don't edit this by hand — logging in writes it. ### `~/.nori/handroll/config.json` — handroll settings | Field | Purpose | |---|---| | `brokerOrg` | The org this client targets | | `loginPreference` | `google` \| `email` \| `skip` | Handroll preserves keys it doesn't own when writing this file, so other tools' entries survive. ## Broker URL resolution Which broker handroll talks to, in priority order: 1. `NORI_SESSIONS_URL` environment variable 2. `NORI_BROKER_URL` environment variable (legacy alias) 3. `brokerOrg` from the config file, as `https://.norisessions.com` 4. `http://localhost:19400` (local-development fallback; no login required) ## Environment variables | Variable | Purpose | |---|---| | `NORI_SESSIONS_URL` | Broker URL override (primary) | | `NORI_BROKER_URL` | Broker URL override (legacy alias) | | `NORI_CDN_BASE` | CDN base for the daily update check | | `NORI_TRANSCRIPT_PATH` | Path to the agent's transcript file, read by `snapshot` and hand-off (tilde-expanded) | | `HANDROLL_LOG_FILE` | Log file path (default: `~/.nori/handroll/log/.log`) | ## Keybindings The prefix key is **`Ctrl-B`** and is **fixed** — there is no setting to change it. Handroll exports `NORI_PREFIX_KEY=ctrl-b` into the wrapped agent's environment so tools inside the session can discover it. The actions behind the prefix are covered in [usage](/sessions/handroll/usage/). ## Next steps - [Usage](/sessions/handroll/usage/) — the Ctrl-B overlay and subcommands. - [Hand-off & resume](/sessions/handroll/handoff-and-resume/) — what moves between terminal and cloud today. --- --- title: Hand-off & resume description: Moving a session between your terminal and the cloud — and what's supported today. status: verified sidebar: order: 2 --- Handroll is the bridge between a local terminal session and the Nori cloud. Full session hand-off — in either direction — is not generally available today. Here is what works now. ## What works today - **Drive a cloud session from your terminal.** `nori-handroll acp --type cloud` (alias: `cloud-acp`) is a stdio ACP adapter: it authenticates, acquires a session from your org's fleet, bridges to it, and releases the session when its input closes. Point any ACP-capable client at it to work in a cloud session without leaving your machine. - **Push local workspace state to your git remote.** `nori-handroll snapshot` pushes an orphan metadata branch (`nori/session//`) containing your uncommitted diff, untracked files, and the agent transcript. It requires a git remote. - **Resume cloud sessions in the cloud.** Cloud sessions resume by replying in their thread, or via `!resume` in Slack — see [checkpoints & resume](/sessions/concepts/checkpoints-and-resume/). ## What's gated The overlay's **Handoff** and **Remote Control** entries — pushing a running local session up to the fleet, and enabling a local session's ACP control socket mid-run — are shown but disabled in release builds, labeled "debug only". The broker likewise rejects hand-off-style session acquisition. (Remote control itself is available in release builds when enabled at launch: `nori-handroll --remote-control`, then connect with `nori-handroll acp --type local`.) :::caution[Status] This reflects the current build: the local ⇄ cloud hand-off flow is still being finished. Don't build a workflow that depends on it; start cloud work from [Slack](/sessions/guides/slack/), the web dashboard, or `nori-handroll acp --type cloud`, and use Handroll's proxy for local runs. ::: ## Next steps - [Usage](/sessions/handroll/usage/) — install, the Ctrl-B overlay, attach and detach. - [Checkpoints & resume](/sessions/concepts/checkpoints-and-resume/) — how cloud sessions outlive their machines. --- --- title: Usage description: Wrap your local agent, work through the overlay, and bridge cloud sessions into local tools. status: draft sidebar: order: 1 --- ## Install and run ```sh curl -fsSL https://norisessions.com/install.sh | bash nori-handroll claude # or codex, gemini ``` The first run asks to log in (Google or email) and installs the session hook for your agent. Then your agent runs exactly as before — handroll sits invisibly in front of it. Updates are checked once a day; `nori-handroll` offers to update itself when a new version ships. ## The overlay Press **Ctrl-B** to open the overlay while a session runs: | Action | What it does | |---|---| | Attach / Detach | switch between running sessions, or leave one running in the background | Detached sessions keep running; reattach with `nori-handroll attach --latest` or pick one with `nori-handroll attach --ui`. :::note Overlay **Handoff** and mid-run **Remote control** are disabled in release builds (debug-only experiments), and the broker rejects hand-off-style acquisition. Remote control works in release only when enabled at launch: `nori-handroll --remote-control`. ::: ## Cloud sessions from your terminal Local → cloud hand-off is not available today. What works: - **Bridge a cloud session into a local ACP client**: `nori-handroll acp --type cloud` acquires a cloud session and speaks ACP over stdio; the session is released when the client disconnects. - **Snapshot workspace state**: `nori-handroll snapshot` pushes an orphan metadata branch (`nori/session//`) — requires a git remote. - **Resume in the cloud**: reply in the session's thread or use `!resume` from Slack. Details and the exact contract: [Handoff and resume](/sessions/handroll/handoff-and-resume/). Which broker handroll talks to, in priority order: `NORI_SESSIONS_URL`, then `NORI_BROKER_URL`, then your configured org (`https://.norisessions.com`), then `http://localhost:19400`. Login is only required for non-local brokers. ## Subcommands ``` nori-handroll run (default) nori-handroll attach [target] reattach to a running session nori-handroll login|logout manage auth (google or email) nori-handroll acp stdio ACP adapter (--type local|cloud) nori-handroll snapshot push workspace metadata for later resume nori-handroll fleet status pool counts (admins) ``` --- --- title: Reference description: Lifecycle states, configuration, and limits. status: draft sidebar: order: 3 --- - [session lifecycle](/sessions/reference/lifecycle/) — states, timeouts, resume tiers - [configuration](/sessions/reference/config/) — every org-level knob - [limits](/sessions/reference/limits/) — the numeric edges, with rationale --- --- title: Configuration description: Every org-level knob — fleet, defaults, Slack policy, workspace — with types and defaults. status: draft sidebar: order: 2 --- Org configuration is edited in **Fleet Setup** (or by agents via the `nori-sessions` CLI). This page will be generated from the configuration schema; until then it lists the settable surface by hand. ## Fleet | Key | Type | Default | Notes | |---|---|---|---| | fleet size | number | 3 | max 10 (higher by arrangement) | | lifecycle timeouts | durations | — | ready max age, claimed idle, session inactivity, provision — defaults and semantics in the [lifecycle reference](/sessions/reference/lifecycle/) | | sessions provider | `sprites` \| `modal` | `sprites` | compute backend; switching drains the pool on next restart | ## Defaults | Key | Type | Default | |---|---|---| | default provider | agent id | `claude` | | enabled providers | list | `[claude]` | | default models | per-provider map | provider defaults | | default skillset | name | none | | default trigger timezone | tz | none | ## Policies | Key | Values | Default | |---|---|---| | commit attribution | `nori` · `none` · `agent` | `nori` | | Slack access mode | `open` · `guarded` · `strict` | — | | Slack agent reach | `channel-only` · `public-member` · `direct-token` | `channel-only` | | Slack DMs / file upload / file download | on/off | on | | distribute Nori access tokens | on/off | on | | distribute infra provider credentials | on/off | off | ## Workspace Workspace mode and repository list control what gets cloned onto each session machine; an org setup script runs on every machine at claim time. --- --- title: Session lifecycle description: The states a session machine moves through, the timeouts that drive them, and the resume contract. status: draft sidebar: order: 1 --- ## Machine states | State | Meaning | |---|---| | `bootstrapping` | VM provisioning and warm-up | | `ready` | warm in the pool, waiting to be claimed | | `claimed` | owned by a session (sub-state: `active` while a turn runs, `idle` between turns) | | `dead` | released or reaped; may be `preserved` for diagnostics | ## Timeouts (org-configurable; these are the defaults) | Timeout | Default | What happens | |---|---|---| | session inactivity | 15 min | the agent process winds down silently; your claim on the machine survives, and a thread reply re-attaches | | claimed idle | 12 h | a long-idle claim is auto-released | | ready max age | 4 h | warm machines are recycled (clamped below the compute provider's VM lifetime) | | provision | 30 min | a machine that hasn't come up is abandoned and retried | | claim queue | 5 min | how long a session request waits for capacity | ## Resume tiers A reply resumes a session through the first tier that applies: 1. **Live claim** — the machine is still yours; the session re-attaches in place, with full working state. 2. **Checkpoint restore** — the machine is gone, but the last consistent checkpoint restores onto a fresh machine. History is preserved; a session is resumable whenever a checkpoint exists and the session wasn't explicitly released. Checkpoints are taken after every agent turn; retention and size caps are in [limits](/sessions/reference/limits/). ## Failure modes - **Fleet at capacity** — requests return HTTP `529` with a retry message; the claim queue holds your spot for up to 5 minutes. - **Broker restart** — sessions and history are preserved; idle sessions reconnect within seconds and affected threads get a restart notice. - **Transport drop mid-turn** — the session recovers or surfaces an error attributed to transport, agent, or broker — reply-to-resume keeps working. --- --- title: Limits description: The numeric edges of the platform, and why each one is where it is. status: draft sidebar: order: 3 --- Stated limits are contracts: if you hit one, this is the behavior you'll see. | Limit | Value | Why | |---|---|---| | Slack file download | 20 MB per file | keeps fetches inside session-side timeouts | | Slack message chunk | 40 K characters | Slack API ceiling, split automatically | | `!resume` picker | 8 sessions | a picker, not a search — older sessions remain resumable from the dashboard | | `!catchup` replay | 40 turns | bounded replay cost in busy threads | | provider-switch bridge | ~12 K characters of transcript | enough context to continue, small enough to move providers quickly | | checkpoint retention | 3 most recent turns | bounded storage per session with full resume fidelity | | checkpoint bundle | warn at 512 MiB, fail at 2 GiB | large artifacts belong in git remotes, not session snapshots | | fleet size | 10 (self-serve) | higher by arrangement | Timeouts and capacity behavior (HTTP `529`, the claim queue) live in the [lifecycle reference](/sessions/reference/lifecycle/). --- --- title: Start description: Sign up and run a first cloud session. status: draft sidebar: order: 1 --- - [quickstart](/sessions/start/quickstart/) — signup to a working agent session --- --- title: Quickstart description: From signup to a working cloud agent session in one sitting. status: draft sidebar: order: 1 --- ## 1. Create your org Sign up at [norisessions.com](https://norisessions.com). Self-serve plans are provisioned automatically: you get a single-tenant broker at `.norisessions.com`. If your org already exists, find it at [login.norisessions.com](https://login.norisessions.com) — sign in and it resolves your memberships. ## 2. Connect an agent provider Open **Integrations** in your dashboard and connect Claude, Codex, or Gemini — an API key or an OAuth login, either works. The [integrations guide](/sessions/guides/integrations/) covers every provider and how credentials are handled. ## 3. Start a session Pick any surface: - **Slack** — install the app from the Integrations checklist, then mention the bot in a channel: `@nori fix the failing CI on main`. It replies in a thread; plain replies continue the conversation. - **Web** — open **Chat** in the dashboard and type. Same sessions, same fleet. - **Terminal** — install [handroll](/sessions/handroll/) and hand a local session off to the cloud. ## 4. Let go Sessions are designed to be abandoned: after 15 minutes of inactivity the agent process winds down silently, but your claim on the machine survives — reply in the thread and it picks up where it left off. Work is checkpointed after every turn. Full contract: [lifecycle reference](/sessions/reference/lifecycle/). --- --- title: Nori Skillsets description: The open-source skill manager — one skillset, installed into any coding agent. status: draft sidebar: order: 3 --- Skillsets (`sks` in your shell, `nori-skillsets` on npm) packages the skills, instructions, subagents, slash commands, and MCP servers your coding agents work with — and installs one canonical skillset into whichever agents you use. Thirteen agents — seven supported, six experimental — come from a single declarative table; a registry at [noriskillsets.dev](https://noriskillsets.dev) (public, plus private per-org registries) handles sharing and versioning. ```sh npm install -g nori-skillsets sks init sks install senior-swe ``` ## Sections - [start/](/skillsets/start/) — install, first skillset, project scoping - [guides/](/skillsets/guides/) — authoring skills, managing skillsets, publishing, MCP - [reference/](/skillsets/reference/) — commands, configuration, supported agents, schemas - [develop/](/skillsets/develop/) — architecture and contributing Source: [github.com/tilework-tech/nori-skillsets](https://github.com/tilework-tech/nori-skillsets). --- --- title: Skills & skillsets description: A skill is one focused ability; a skillset is a complete, installable agent configuration built from skills and more. status: verified sidebar: order: 1 --- Two words carry the whole model: **skill** and **skillset**. ## Skill A **skill** is one focused ability an agent can use — a directory containing a `SKILL.md` (frontmatter plus markdown instructions), sometimes with helper scripts. Skills are small and composable, and move individually too: `sks upload-skill` publishes one, and `sks download-skill` pulls one into a skillset's manifest. ## Skillset A **skillset** is a complete, installable agent configuration. It bundles: - **Skills** — the abilities above - **Instructions** — an `AGENTS.md` that becomes the agent's native instructions file on install - **Subagents** — specialized sub-agents the main agent can call - **Slash commands** — custom commands - **MCP servers** — tool servers to connect A skillset is described by a `nori.json` manifest (`name` and `version` required — layout in [Authoring skills](/skillsets/guides/authoring/)) and, once downloaded, lives in your library under `~/.nori/profiles//`. ## Portability The value of the bundle is that one skillset works across every supported agent. You author it once; the client [translates](/skillsets/concepts/translation-and-agents/) it into each agent's own on-disk format. That's what makes a skillset portable rather than tied to one tool. ## Next steps - [Translation & agents](/skillsets/concepts/translation-and-agents/) — how one skillset becomes each agent's native files - [The registry](/skillsets/concepts/the-registry/) — where skillsets are shared and versioned - [Authoring skills](/skillsets/guides/authoring/) — the `SKILL.md` contract and skillset layout --- --- title: The registry description: noriskillsets.dev is the registry the client installs from — public and per-org private, versioned, searchable. status: verified sidebar: order: 3 --- The client installs skillsets from a **registry** — [noriskillsets.dev](https://noriskillsets.dev) — a searchable catalog of skillsets, skills, and subagents. ## Public and org registries - The **public registry** at `noriskillsets.dev` hosts community and Nori skillsets. Any logged-in user can publish; new packages are moderated before they appear in search. Downloading from it needs no account. - **Private org registries** live at `.noriskillsets.dev` and require org membership. Reference their packages as `org/name`, or point a command at one with `--registry `. ## Auth `sks login` authenticates with email/password, Google SSO (`--google`), or an org API token (`--token`); auth state is stored under the `auth` key of `~/.nori-config.json`. In CI, set `NORI_API_TOKEN` instead — tokens are self-describing (`nori__<64 hex>`), so the org registry URL is derived from the token itself. ## Versioning Packages are semver-versioned via `nori.json`. Pin a version with `download @`; inspect what's available with `--list-versions` on `download` or `upload`. :::note There is no `update` command. Updating is re-downloading: run `download` again, or rely on `redownloadOnSwitch` (enabled by default), which refreshes a skillset from the registry every time you switch to it. ::: ## Next steps - [Publishing](/skillsets/guides/publishing/) — uploading, version bumps, and CI tokens - [Commands](/skillsets/reference/commands/) — `download`, `upload`, `login`, and the rest --- --- title: Translation & agents description: How one skillset becomes each agent's native on-disk configuration, and which agents are supported. status: verified sidebar: order: 2 --- A skillset is portable because the client **translates** it into whatever format each agent expects on disk. You write once; every agent gets a native version. ## The translation step When you install or switch a skillset, the client writes each component into the target agent's own layout: - **Instructions** → the agent's own instructions file (`CLAUDE.md` for Claude Code, `GEMINI.md` for Gemini CLI, `copilot-instructions.md` for GitHub Copilot, `AGENTS.md` for the rest) - **Skills, subagents, slash commands** → the agent's directories and formats (e.g. `.claude/agents/` and `.claude/commands/` for Claude Code) - **MCP servers** → from one canonical `mcp/.json` (with `${env:VAR}` placeholders) into the agent's config shape — `.mcp.json` for Claude Code, `config.toml` for Codex — with per-agent merge rules The same skillset therefore behaves consistently whether the agent is Claude Code, Cursor, or Gemini CLI. :::note Translated files are managed: the client tracks them with hash manifests and rewrites them on the next switch. Edit the skillset in `~/.nori/profiles/`, not the agent's directory — see [Managing skillsets](/skillsets/guides/profiles/). ::: ## The agent table Every agent is one declarative row in a single table — 13 agents today, 7 supported and 6 experimental, with `claude-code` as the default. The full matrix of names and tiers is generated from that table: see [Supported agents](/skillsets/reference/agents/). You can target several agents with one skillset: `sks config --agents` sets your defaults, and `--agent ` targets one explicitly — see [Managing skillsets](/skillsets/guides/profiles/). ## Next steps - [Supported agents](/skillsets/reference/agents/) — every agent by support tier, with its `--agent` name - [Managing skillsets](/skillsets/guides/profiles/) — switching, multiple agents, and cleanup - [Skills & skillsets](/skillsets/concepts/skills-and-skillsets/) — what's in the bundle being translated --- --- title: Develop description: Architecture and contributing. status: draft sidebar: order: 4 --- - [architecture](/skillsets/develop/architecture/) — layering, the agent table, the install model --- --- title: Architecture description: One declarative agent table, layered modules, and a registry client — how sks is built. status: draft sidebar: order: 1 --- ``` commands / flows (presentation) │ ▼ core (policy: upload resolution, versioning, auth resolution) │ ├── packaging (tar, atomic swap, provenance, registry lookup) ├── features (agent table, loaders, install engine, manifests) └── api (registrar client, auth ladder) │ ▼ utils ``` Dependencies point strictly downward; `core` never imports from the CLI layer, and interactive flows are pure presentation with injected callbacks. ## The agent table Every supported agent is one row in a declarative table: its directory, instructions filename, MCP emitter, capabilities, and support tier (`supported` / `experimental`). Adding an agent means adding a row plus tests — no conditionals in shared code. Help text and the agent matrix derive from the table. ## Install model A skillset in `~/.nori/profiles/` is the source; activation translates it through per-agent loaders (instructions, skills, subagents, slash commands, MCP) into the agent's directory, marks it with `.nori-managed`, and records SHA-256 manifests per (agent, directory) for local-change detection. Non-destructive invariants: settings files merge rather than clobber, external files get `.pre-nori` backups, updates swap atomically, downloads verify checksums. ## Registry client The registrar API is npm-shaped: packuments, dist-tags, tarballs. Auth resolves through a strict ladder — `NORI_API_TOKEN` env, config API token, unexpired Firebase ID token, refresh-token exchange, legacy password — and org-scoped tokens are never sent cross-registry. ## Contributing Node 22, Commander, vitest (colocated tests), eslint + prettier + tsc. The repo's AGENTS.md carries a test-enforced "How to Close the Loop" section — every change class has a concrete end-to-end verification path. Releases are CI-only via npm trusted publishing (`skillsets-v*` tags → latest; main → `@next`). --- --- title: Guides description: Authoring, managing, publishing, and MCP. status: draft sidebar: order: 2 --- - [authoring skills](/skillsets/guides/authoring/) — SKILL.md, the lint contract, skillsets - [managing skillsets](/skillsets/guides/profiles/) — switching safely, multi-agent, cleanup - [publishing](/skillsets/guides/publishing/) — registries, versions, CI tokens - [MCP servers](/skillsets/guides/mcp/) — author once, translated per agent --- --- title: Authoring skills description: Write a SKILL.md that passes lint, package it in a skillset, and keep it honest. status: draft sidebar: order: 1 --- A skill is a directory containing `SKILL.md`: YAML frontmatter plus markdown instructions. ```markdown --- name: reviewing-migrations description: Use when reviewing database migration PRs — checks reversibility, locks, and backfill safety. --- Read the migration files before commenting... ``` The contract (enforced by `nori-lint`): | Rule | Constraint | |---|---| | `name` | required, ≤64 chars, `lowercase-kebab-case` | | `description` | required, ≤1024 chars — this is what agents use to decide relevance | | `` block | present | | length | ≤150 lines total | `nori-lint` ships 12 deterministic rules plus 10 LLM-judged quality rules (duplicate sections, first-person voice, obvious instructions, unexplained URLs…). Run it before publishing. ## Skillsets A skillset bundles skills with instructions and more, described by `nori.json` (`name` and `version` required): ``` my-skillset/ ├── nori.json # name, version, dependencies, requiredEnv ├── AGENTS.md # instructions installed into the agent ├── skills//SKILL.md ├── subagents/ ├── slashcommands/ └── mcp/.json # canonical MCP config, translated per agent ``` Create with `sks new`, adopt an existing folder with `sks register`, or develop against a git checkout with `sks link ` (symlinks the skillset into your library; `unlink` removes it). ## Importing from GitHub ```sh sks external owner/repo@skill-name --skillset my-skillset ``` Clones shallowly, discovers `SKILL.md` files, and records provenance (source URL, ref, timestamp) in `nori.json`. --- --- title: MCP servers description: Author MCP config once; sks translates it for every agent's format. status: draft sidebar: order: 4 --- Agents disagree about where MCP config lives — `.mcp.json`, Codex's `config.toml`, Gemini/VS Code/Zed `settings.json`, Cursor's `mcp.json`. Skillsets makes you author it once. Each server is one canonical file in the skillset's `mcp/` directory: ```json { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "${env:GITHUB_TOKEN}" } } ``` On switch, sks emits the right format for each configured agent. `${env:VAR}` placeholders are rewritten per agent's convention, and the variables land in the skillset's `requiredEnv` so users know what to set. Already have MCP servers configured on disk? Pull them into a skillset: ```sh sks import-mcp my-skillset ``` --- --- title: Managing skillsets description: Switch safely, know what's active where, and reset when needed. status: draft sidebar: order: 2 --- Your library lives at `~/.nori/profiles/`. Activation writes a translated copy into each configured agent's directory and drops a `.nori-managed` marker there. ## Switching ```sh sks switch # interactive picker sks switch my-swe # by name ``` Before switching away, sks compares the installed files against their recorded hashes; if you edited managed files it stops and tells you — `--force` discards the edits. With `redownloadOnSwitch` enabled (the default), the skillset is refreshed from the registry first. ## Knowing what's active ```sh sks current # active skillset for an agent sks list # your library sks list-active # walks up from CWD showing every managed directory sks dir # open your skillset library (~/.nori/profiles) ``` ## Multiple agents `sks config --agents` sets your default agents; switches and installs broadcast to all of them. Target one explicitly with `--agent `. ## Cleaning up - `sks clear` — remove managed files and deactivate - `sks clear-current` — clear every managed directory from CWD upward - `sks factory-reset ` — wipe the agent's directory entirely (interactive only) Files sks touched outside its own directories (like `~/.claude/settings.json`) are backed up to `.pre-nori` on first install and restored on uninstall. --- --- title: Publishing description: Share skillsets and skills on the public registry or your org's private one. status: draft sidebar: order: 3 --- ```sh sks upload my-skillset # whole skillset sks upload-skill my-skill # a single skill ``` Versions live in `nori.json` and follow semver. On a name collision the client byte-compares content and offers: bump the patch version (default), view a diff, or cancel. Non-interactive uploads must pass `--version`. ## Public vs org registries - **Public** ([noriskillsets.dev](https://noriskillsets.dev)) — any logged-in user can publish; new packages are moderated before they appear in search. - **Org** (`.noriskillsets.dev`) — membership required; reference packages as `org/name` or use `--registry`. ## Tokens for CI API tokens are self-describing — `nori__<64 hex>` — and scope to one registry. Set `NORI_API_TOKEN` in CI and every command authenticates with zero configuration; the registry URL is derived from the token itself. Long-lived tokens never leave your machine's env; interactive logins use Firebase tokens cached for ~55 minutes. --- --- title: Reference description: Commands, configuration, and the agent matrix. status: draft sidebar: order: 3 --- - [commands](/skillsets/reference/commands/) — the sks surface - [configuration](/skillsets/reference/config/) — ~/.nori-config.json and env vars - [supported agents](/skillsets/reference/agents/) — generated from the agent table --- --- title: Supported agents description: Every agent sks can install into, by support tier. status: verified editUrl: https://github.com/tilework-tech/nori-docs/blob/main/scripts/regen-skillsets-reference.mjs sidebar: order: 3 --- One skillset installs into any of these. `--agent ` targets one; `sks config --agents` sets your defaults. ## Supported | Agent | `--agent` name | |---|---| | Claude Code | `claude-code` | | Codex | `codex` | | Cursor | `cursor-agent` | | Gemini CLI | `gemini-cli` | | GitHub Copilot | `github-copilot` | | Goose | `goose` | | Pi | `pi` | ## Experimental | Agent | `--agent` name | |---|---| | Cline | `cline` | | Droid | `droid` | | Kilo Code | `kilo` | | Kimi CLI | `kimi-cli` | | OpenCode | `opencode` | | OpenClaw | `openclaw` | --- --- title: Commands description: The sks command surface at a glance. status: draft sidebar: order: 1 --- *This page will be generated by walking the CLI's command definitions (which also fixes the shell-completion drift). Hand summary meanwhile:* ``` registry search · download · upload · install · download-skill · upload-skill · download-subagent · external lifecycle init · switch · clear · clear-current · factory-reset · config · watch authoring new · fork · register · link · unlink · edit · import-mcp info list · list-active · current · dir · install-location · completion auth login · logout ``` Global flags on every command: `--install-dir `, `--agent `, `--non-interactive` (automatic under CI or non-TTY stdin), `--silent`. --- --- title: Configuration description: The ~/.nori-config.json keys and the env vars that override them. status: draft sidebar: order: 2 --- Config lives at `~/.nori-config.json` (schema-validated; unknown keys are dropped). Set values with `sks config` or `sks login`. | Key | Default | Meaning | |---|---|---| | `defaultAgents` | `["claude-code"]` | agents that installs/switches broadcast to | | `installDir` | home | default activation directory | | `activeSkillset` | — | what's currently active | | `autoupdate` | disabled | self-update without asking | | `redownloadOnSwitch` | enabled | refresh from registry before switching | | `claudeCodeStatusLine` | enabled | install the Nori statusline for Claude Code | | `sendSessionTranscript` | enabled | transcript upload via `sks watch` | | `garbageCollectTranscripts` | — | prune old local transcripts | | `auth` | — | managed by `sks login` / `logout` | ## Environment variables | Variable | Effect | |---|---| | `NORI_API_TOKEN` | authenticate as `nori__`; registry derived from the token — zero-config CI | | `NORI_GLOBAL_CONFIG` | relocate config and the profiles library (test/e2e isolation) | | `NORI_SKILLSETS_COMMIT_ATTRIBUTION` | `nori` (default) · `none` · `agent` — controls git commit attribution hooks for Claude Code | | `NORI_NO_ANALYTICS` | `1` disables anonymous usage analytics | --- --- title: Start description: Install sks and activate a first skillset. status: draft sidebar: order: 1 --- - [install](/skillsets/start/install/) — install, first skillset, the one rule, login --- --- title: Install description: Install sks and activate your first skillset. status: draft sidebar: order: 1 --- ```sh npm install -g nori-skillsets ``` Node 22+, macOS and Linux. Three aliases install: `nori-skillsets`, `nori-skillset`, and `sks`. ## First skillset ```sh cd your-project sks init # set up in this project sks install senior-swe # download and activate in one step ``` `init` operates on the directory you run it in, so skillsets are scoped per-project (set a global default with `sks config`). `install` downloads into your library at `~/.nori/profiles/` and activates for every configured agent; use `download` + `switch` to do those separately. ## The one rule Installed agent directories (like `.claude/`) are managed: your manual edits there are replaced on the next switch. Make changes in the skillset itself — `~/.nori/profiles//` — or in a fork: ```sh sks fork senior-swe my-swe sks switch my-swe ``` ## Logging in (optional) Anonymous use of the public registry needs no account. For publishing or a private org registry: ```sh sks login # email/password, --google, or --token ``` For CI and token details, see [publishing](/skillsets/guides/publishing/). --- --- title: Core concepts description: The five words that describe every Nori setup — agent, harness, provider, integrations, compute — plus the managed/composable split. --- Every Nori setup is described by the same handful of ideas. Learn these once and the rest of the docs read easily. ## Agent The thing doing the work — a coding agent that reads your repo, edits files, runs commands, and reports back. Nori is deliberately **agent-agnostic**: it runs many agents rather than shipping its own. ## Harness The program that hosts an agent's loop — Claude Code, Codex CLI, Gemini CLI, and others. Nori speaks to harnesses through a common surface (including the **Agent Client Protocol, ACP**) so you can swap the harness without changing your workflow. *Any harness.* ## Provider (token) The model API behind the agent, and the credentials for it. You **bring your own token** — Claude, OpenAI, Gemini, or another. *Any token provider.* ## Integrations The outside world the agent can touch — Slack, GitHub, Google Workspace, Linear, and more — wired in per org. *Any integrations.* ## Compute Where the agent actually runs. Managed Sessions place each run on a secure cloud VM from a pluggable backend (Sprites or Modal today), selected per fleet. The compute layer is designed to be swappable. *Eventually, any compute.* ## Two altitudes: managed vs. composable Nori is the same idea at two levels: - **Managed** — [Sessions](/sessions/) runs the whole loop for you: it picks the machine, holds the credentials, and streams results to Slack. Fully-managed agents, zero infrastructure. - **Composable** — the open-source [CLI](/cli/) and [Skillsets](/skillsets/), plus [Handroll](/sessions/handroll/), let you assemble your own multi-agent cloud from the same primitives, on your own terms. Keep these five words in mind — *agent, harness, provider, integrations, compute* — and every page tells you which one it's about. --- --- title: Quickstart description: The fastest path to a running agent — pick the entry point that matches how you want to work. --- There are two ways into Nori. Pick the one that matches how you want to work; you can use both. ## Path A — the open-source CLI (fastest, local) Get a multi-provider coding agent running in your terminal. ```bash npm install -g nori-ai-cli nori ``` Pick a provider (Claude, Codex, Gemini, or any ACP agent), point it at a repo, and drive your first turn. Full walkthrough: **[CLI → Install](/cli/start/install/)**. ## Path B — managed Sessions (cloud, from Slack) Run a fully-managed agent in a secure cloud sandbox, started from a Slack thread. 1. Connect the Nori Slack app to your workspace. 2. Start a session in a channel and hand the agent a task. 3. Watch it work in the thread; release it when you're done. Full walkthrough: **[Sessions → Overview](/sessions/)**. ## Add skillsets (optional, either path) Make any agent better with packaged configurations from the open registry: ```bash npm install -g nori-skillsets ``` Then browse and install from **[Skillsets](/skillsets/)**. :::tip Not sure which path fits? If you want to *try an agent right now*, start with the CLI. If you want *agents your team can delegate to*, start with Sessions. ::: --- --- title: What is Nori description: Nori takes any team of AI coding agents cloud-native — any harness, any token provider, any integrations, any compute. --- Nori is the most customizable stack for **background cloud agents**: proactive, programmatic agent runs inside secure sandboxes, plus the open-source tooling to drive any agent from anywhere. One idea runs through all of it — take any team of AI coding agents **immediately cloud-native**, with any harness, any token provider, any integrations, and (eventually) any compute. ## The problem Coding agents are powerful on a laptop and stranded there. Moving them to the cloud usually means picking one vendor's harness, one model provider, one opinionated runtime — and rebuilding your workflow around it. Teams that want many agents, running proactively, on their own compute, with their own integrations, end up gluing it together themselves. ## Who Nori is for - **Teams adopting agents at scale** who want managed agents they can start from Slack and trust in a sandbox — without babysitting a VM. - **Builders of multi-agent systems** who want to bring their own harness, model keys, and compute, and compose the pieces themselves. - **Open-source users** who just want one great terminal for every agent, and a registry of configurations that make those agents better. ## How you use it Nori comes in two ways in — a fully-managed product and a composable open-source core. - **[Sessions](/sessions/)** — a managed cloud runtime. Start an agent in a secure sandbox from a Slack thread, hand it a task, and get results back. This is the "fully-managed agents in Slack" path. - **[Handroll](/sessions/handroll/)** — the local terminal proxy that fronts an interactive agent and connects it to the cloud, with attach / detach / hand-off. - **[CLI](/cli/)** *(open source)* — one native terminal for every provider: Claude, Codex, Gemini, or any ACP-compatible agent. Bring your own token. - **[Skillsets](/skillsets/)** *(open source)* — install packaged agent configurations from an open registry; one skillset is translated to every agent's on-disk format. The managed product and the open-source core are the same idea at two altitudes: the product runs it for you; the open source lets you run it yourself. ## Get started - New here? Follow the **[Quickstart](/start/quickstart/)**. - Want the mental model first? Read **[Core concepts](/start/concepts/)**. ## Can't find what you're looking for? Each product has its own overview and reference. If something is missing or unclear, that is a documentation bug — see the **[FAQ](/resources/faq/)** or the [GitHub org](https://github.com/tilework-tech).