Nsys Cortex

CLI Reference

The CLI is available as nsys-cortex after linking (npm link -w packages/cortex-cli), or via npx nsys-cortex / npm run cortex. The nsys-cortex launcher executes the compiled bundle (dist/index.js, produced by npm run build) on plain node; if the bundle hasn't been built it falls back to running the TypeScript source through tsx. See Development for the build details.

All commands accept a global --mode <auto|dev|docker|firecracker> flag (default: auto — docker when a Docker daemon and the neuron image are present, otherwise dev; firecracker is never chosen automatically; config runs without resolving a mode). docker runs the agent in a hardened container (needs Docker + npm run build:image; see Configuration → Docker mode); firecracker is the MicroVM boundary (Linux + KVM + root).

Dev mode requires explicit opt-in. Set NSYS_CORTEX_ALLOW_INSECURE_DEV=1 before running any subcommand in dev mode — the CLI refuses otherwise. Dev mode is not a security boundary; the agent runs as your user. See Configuration → Security for details.

On first run, ~/.nsys-cortex/config.json is auto-created with default settings. Edit it to set API keys and defaults — see Configuration for details.

The exec, install, shell, and chat commands auto-create the neuron if it is not already running. If a neuron exists from another process that has since exited, it is restarted in the current process; if that process is still alive, the command refuses with its pid (Error: Neuron <id> is running under another process (pid N, …), exit 1) — send commands through that process (the HTTP API server) or run nsys-cortex destroy <id> first.

The ASCII banner is shown for interactive commands (chat, shell), --help, and all subcommand help pages (<cmd> --help, help <cmd>). Non-interactive commands output clean results without the banner.

Installing the CLI itself is the README's "Install" section: the release-asset scripts scripts/install/install.sh (Linux, macOS) and scripts/install/install.ps1 (Windows) run npm install -g @nsys-cortex/cli@<pinned version> with your own Node 24, verify nsys-cortex --version and report whether Docker is present; the install command below installs packages inside a neuron.

Commands

nsys-cortex --version                          Show version
nsys-cortex --help                             Show help and list all commands
nsys-cortex create <agent-id>                  Create a new neuron sandbox
nsys-cortex exec <agent-id> <command...>       Execute command in neuron (-l/--language bash|node|python)
nsys-cortex install <agent-id> <pkg>           Install package (--via apt|npm|pip)
nsys-cortex snapshot <agent-id>                Snapshot neuron (refused outside Firecracker mode, exit 1; not yet supported under the jailer either)
nsys-cortex destroy <agent-id>                 Destroy neuron (--wipe to delete overlay; exit 1 for an unknown id; says whether the overlay was kept)
nsys-cortex list                               List neurons (running + stopped, includes cross-process)
nsys-cortex shell <agent-id>                   Interactive shell (exit with Ctrl+D or "exit")
nsys-cortex chat <agent-id>                    AI chat session (markdown output, persistent history)
nsys-cortex config <get|set|unset|path>        Inspect and edit ~/.nsys-cortex/config.json
nsys-cortex test-persistence <agent-id>        Run automated persistence test

exec exits with the exit code of the command it ran inside the neuron (1 when the neuron could not be reached) and writes only that command's stdout to stdout — its own status lines (Starting neuron …, Exit code: N (Tms), Error: …) go to stderr — so nsys-cortex exec id 'test -f x' && … and out=$(nsys-cortex exec id cat f) work.

<agent-id> names one directory under the overlay store, so it must be a single path component: no /, not . or .., and no whitespace or control characters (the HTTP API is stricter still: [A-Za-z0-9_-], at most 64). Anything else is refused before the filesystem is touched, and an existing overlay directory with such a name (created before this rule) is ignored by list with a warning — rename it (mv 'foo bar' foo-bar) to keep using it.

Config Command

config reads and edits ~/.nsys-cortex/config.json without hand-editing JSON — the friendly path to setting API keys and defaults.

nsys-cortex config get                                             # whole config (secrets redacted)
nsys-cortex config get llm.defaultProvider                         # one value by dotted path
nsys-cortex config set llm.providers.anthropic.apiKey sk-ant-...   # set an API key
nsys-cortex config set llm.defaultProvider openai                  # switch active provider
nsys-cortex config unset llm.providers.grok.apiKey                 # remove a value
nsys-cortex config path                                            # print the file location

Install Command

The install command installs packages inside the neuron (in dev mode the neuron is the host, so there it installs on the host). Packages persist across neuron restarts via the overlay filesystem. The CLI and the HTTP API build the same command (buildInstallCommand() in @nsys-cortex/host), so what one refuses the other refuses too: the package name must match [@a-z0-9][a-z0-9._/-]{0,213} (no whitespace, no shell metacharacters, no version specifier) and is single-quoted into the command after --.

Manager dev mode (the host) docker and firecracker mode (the Alpine neuron image) Install location
npm (default) npm install -- '<pkg>' npm install -- '<pkg>' <workspace>/node_modules/
pip pip3 install -- '<pkg>' pip3 install --user --break-system-packages -- '<pkg>' — the image's Python is PEP 668-managed and its system site-packages unwritable the host's Python / /home/agent/.local (persists in the overlay's home)
apt apt-get install -y -- '<pkg>' refused — the image is Alpine Linux (apk), non-root, with a read-only root; use npm or pip the host's packages

A refusal (apt outside dev mode, an unknown --via, a malformed name) prints the reason on stderr and exits 1 before any neuron starts. A failed install shows the neuron's stderr, then Failed to install <pkg> via <via>: exit code N, and exits 1; success prints Package <pkg> installed successfully (Tms). Status lines go to stderr; the package manager's own stdout stays on stdout (truncated at 2000 bytes).

Shell

The shell command opens an interactive shell inside a neuron.

The greeting shows the workspace path:

Requires node-pty in the neuron image (docker mode — npm run build:image compiles it) or in the VM rootfs (Firecracker mode). If node-pty is unavailable, pty_spawn returns a clear error and the shell exits 1. A PTY command that fails mid-session ends the shell with the agent's error on stderr (PTY read failed: …, Shell connection lost: …) and exit code 1; "Shell closed" with exit 0 means the shell inside the neuron ended (exit or Ctrl+D).

Chat Mode

The chat command supports both interactive and non-interactive modes:

Options

Option Description
-m, --model <model> LLM model to use
-p, --message <msg> Send a single message and exit (non-interactive)
--json Output result as JSON object (non-interactive only)
--admin Enable the full tool preset (bash, process_*, elevated). Off by default.
-c, --continue Resume the most recent saved session for this agent
--session <id> Resume a specific session by id (must belong to this agent)
--output-schema <file> Constrain the final response to a JSON Schema file (structured outputs; non-interactive only)
--image <file> Attach an image to the message (repeatable; png/jpg/gif/webp, max 5MB each; non-interactive)
--think [budget] Enable extended thinking / reasoning. Optional integer budget in tokens (default 4000). Anthropic, OpenAI, Gemini.
--approve <classes> Non-interactive only: grant approval requests for these effect classes without asking (comma-separated, or all); every other request is denied (ADR-012, #16).

In interactive chat, attach images inline by referencing them as @path/to/image.png in your message — existing image files are loaded and attached (the @token stays in the text so the model knows which attachment is which); non-existent paths are treated as plain prose. Vision requires a vision-capable model — text-only local models will ignore or garble image input.

By default, chat exposes the coding preset: read, write, edit and bash (the shell tool, under the standard command policy). Pass --admin to also expose process_start/list/read/kill, apply_patch, elevated, the memory and session tools, etc. Note that --admin is not what grants shell access — coding already has it; in dev mode (not a security boundary) a prompt-injected agent can run commands as your user under either preset. readonly is the preset without bash.

If MCP servers are configured (mcpServers in config.json), their tools load at chat startup on top of the active preset. With a few tools the greeting shows [mcp] N external tool(s) loaded; above the defer threshold (default 10) it shows [mcp] N external tool(s) available on demand via mcp_find_tools — the agent then loads only the tools it actually needs, saving the context their schemas would otherwise cost on every turn. A server that fails to connect is skipped with a logged warning; the chat starts regardless. Only connect MCP servers you trust: their tools run outside the neuron sandbox. See Agent Toolkit → MCP Integration.

Extended thinking

Anthropic, OpenAI, and Gemini all expose a "thinking" or "reasoning" mode where the model spends extra tokens reasoning before answering. --think enables it:

nsys-cortex chat my-agent --think                  # 4000-token thinking budget (default)
nsys-cortex chat my-agent --think 8000             # explicit 8000-token budget
nsys-cortex chat my-agent -p "plan this" --think

The interactive chat renders streaming thinking as dim italic with a [thinking] gutter before the response:

You> Plan a path through this codebase

Cortex>
  [thinking]
  I should look at the package structure first, then trace the entry point…

Here's the plan: …

Per-provider behavior:

Provider Mapping Reasoning text streamed? Usage bucket
Anthropic (Claude 4.x) thinking: { budget_tokens: N } Yes — full text + HMAC signature Folded into outputTokens
OpenAI (o-series, GPT-5) reasoning_effort: low / medium / high (bucketed) Only summaries (GPT-5) reasoningTokens
Gemini (2.5+, 3.x) thinkingConfig.thinkingBudget: N + includeThoughts Yes — thought summaries reasoningTokens
Grok (xAI) Inherits OpenAI Chat Completions wiring Provider-dependent reasoningTokens if reported
Ollama reasoning_effort (bucketed; xhigh on Qwen3.8) — honoured by the loaded model or ignored reasoning_content deltas when the server's reasoning parser emits them reasoningTokens if reported
vLLM reasoning_effort (bucketed; xhigh on Qwen3.8) — honoured by the loaded model or ignored reasoning_content deltas when the server's reasoning parser emits them reasoningTokens if reported
LM Studio reasoning_effort (bucketed; xhigh on Qwen3.8) — honoured by the loaded model or ignored reasoning_content deltas when the server's reasoning parser emits them reasoningTokens if reported
SGLang reasoning_effort (bucketed; xhigh on Qwen3.8) — honoured by the loaded model or ignored reasoning_content deltas when the server's reasoning parser emits them reasoningTokens if reported
Mistral Magistral uses a non-OpenAI-compat surface — dropped n/a n/a

Budget-to-effort mapping for OpenAI / Grok (Chat Completions has no numeric knob): ≤1024 → low, ≤4096 → medium, otherwise high — xhigh instead of high on Qwen3.8, whose chat template rejects high. Without --think nothing is sent and the server's default effort applies (the KB's vLLM box sets low).

Constraints (intentional):

When tool use is involved, the agent loop preserves Anthropic's HMAC signature on every thinking block across turns. Without that round-trip, subsequent tool-use turns 400 with thinking blocks must be preserved in subsequent turns. OpenAI and Gemini don't sign their reasoning blocks; the agent loop still tracks them for in-run continuity but they're cheap to drop. This is handled transparently — you don't need to think about it (no pun intended), but it's why thinking blocks live in the session transcript as structured JSON content rather than raw text.

Non-Interactive Examples

# Single message, text output
nsys-cortex chat my-agent -p "What is 2+2?"

# Single message, JSON output
nsys-cortex chat my-agent -p "List files" --json

# Piped input
echo "Generate a Dockerfile" | nsys-cortex chat my-agent

# File as input
cat prompt.txt | nsys-cortex chat my-agent

# Capture output in a variable
RESULT=$(nsys-cortex chat my-agent -p "What files are in /tmp?")

# Pipe output to a file
nsys-cortex chat my-agent -p "Generate a Dockerfile" > Dockerfile

# Structured output: response conforms to a JSON Schema file
# (see docs/agent-runtime.md#structured-outputs for per-provider behavior;
#  note the chat tool preset counts as "tools present" on Anthropic/Gemini)
nsys-cortex chat my-agent -p "Summarize the repo" --output-schema summary.schema.json | jq .

# Vision input: attach images (repeatable flag)
nsys-cortex chat my-agent -p "what changed between these screenshots?" \
  --image before.png --image after.png

# Interactive: reference image files inline with @
#   You> compare @designs/mock.png with @screenshot.png

JSON Output Format

When --json is used, the output is a single JSON object:

{
  "agentId": "my-agent",
  "sessionId": "...",
  "model": "claude-opus-5-5",
  "response": "The answer is 4.",
  "totalTurns": 1,
  "usage": { "inputTokens": 412, "outputTokens": 89 },
  "toolCalls": [
    { "name": "bash", "input": { "command": "echo $((2+2))" }, "durationMs": 150 }
  ]
}

Approvals

A tool whose effect class agent.approval.requireFor lists (default destructive; NSYS_CORTEX_APPROVAL_REQUIRE_FOR), a tool whose policy requires approval, or elevated at the ask level runs only after an explicit approval (ADR-012, #16). In interactive chat the request is asked at the prompt — ⚠ approval: <tool> (<class>) <input> — allow? [y/N] — y runs it, anything else (Enter included) denies it, and the model is told denied: <reason>; the policy's timeout (NSYS_CORTEX_APPROVAL_TIMEOUT_MS, 300 s) and Ctrl-C end the wait as a deny. Non-interactive chat (-p, piped stdin) denies every request unless --approve <class[,class]> or --approve all grants its class — a pipe never approves by accident; a denied request is noted on stderr.

Interactive Chat Commands

During an interactive chat session, the following commands are available:

Command Description
/help List all chat commands
/exit End the chat session and return to the terminal
/quit Alias for /exit
/clear Reset the session — starts a fresh session, discards history
/model [name] Show the current model + registered models, or switch mid-session
/cost Cumulative token usage for the session lifetime (input/output/cache/reasoning; survives --continue)
/save [path] Export the transcript as markdown (default ./cortex-chat-<id>-<date>.md)
/sessions List saved sessions for this agent (marks the current one)
/context Display context window usage (tokens, budget, compaction)
↑ / ↓ Browse input history (persisted per neuron across sessions)
Ctrl-C During a response: cancel the in-flight run. At the prompt: exit

An unknown /word (/bogus, /bogus with args) prints Unknown command /bogus — type /help for the list and costs no model turn; a message that starts with a path (/etc/hosts tells what?) is sent as typed, since the slash-word is followed by /, not a space. Non-interactive mode (-p, piped stdin) sends every line as a message, as before.

/model <name> takes effect on the next message — switching mid-session keeps the conversation history, so you can draft with a cheap local model and switch to a flagship for the final pass. Names not registered in the context-budget table are passed to the provider verbatim (with a 128k default budget), which is how operator-chosen Ollama/vLLM/LM Studio model names work. /cost reports the per-bucket token counts the providers billed, accumulated over the session's whole lifetime — it's read from the persisted session usage, so resuming with --continue keeps the running total rather than resetting; dollar cost depends on each provider's pricing. /save exports everything the faithful transcript stores — user/assistant text, tool calls with inputs, abbreviated tool results, and thinking blocks — as a readable markdown file (mode 0o600).

While the agent is working, a spinner (⠋ thinking… / ⠹ running…) shows activity — responses are buffered and rendered as markdown once complete, so the spinner is what tells you it's still going. Ctrl-C mid-response aborts the in-flight request and any running tool (cancelled cleanly, not killed) and returns you to the prompt with the session intact; Ctrl-C at an idle prompt exits. The spinner is suppressed on non-TTY output so piped/redirected runs stay clean.

Session Persistence & Resume

Chat transcripts are persisted to ~/.nsys-cortex/sessions/ (per-agent JSONL, mode 0o600; override the location with NSYS_CORTEX_SESSION_STORAGE_DIR), so conversations survive across CLI invocations and machine restarts.

nsys-cortex chat my-agent                    # start a fresh session
nsys-cortex chat my-agent --continue         # resume the most recent session for this agent
nsys-cortex chat my-agent -c                 # short form of --continue
nsys-cortex chat my-agent --session ses-abc  # resume a specific session by id

On resume, the last few turns are replayed so you have context for where you left off. Use /sessions inside a chat to list this agent's saved sessions and copy an id for --session.

Persistence is faithful: tool calls, their results, and attached images are stored and reconstructed on resume (not just the assistant's prose), so a resumed agent still "remembers" the files it read, commands it ran, and screenshots it saw. Note that image attachments are stored base64-inline in the JSONL transcript, so sessions with many images grow the storage dir accordingly (the 24h maxAge sweep bounds this over time). Extended-thinking blocks are recorded for transcript fidelity but are not replayed back to the model on reload — a provider's reasoning signature is bound to its original request and the tool-use/result pairs already carry the actions. Expired sessions (older than the 24h maxAge, configurable via NSYS_CORTEX_SESSION_MAX_AGE) are skipped and cleaned up on load.

Suggestion Hints

After each AI response, dim "ghost text" may appear after the You> prompt with a contextual next-step suggestion:

You> run build and tests          ← dim ghost text
Key Action
→ (right arrow) Accept suggestion — fills it into the input
Tab Cycle through available suggestions (up to 3)
Any other key Clear ghost text, proceed with normal typing

Suggestions are generated heuristically based on the agent's tool calls and response:

Condition Suggestion
Any tool had a non-zero exit code fix the error
Tests ran and failed fix failing tests
Tests ran and passed run tests again
Files were written or edited run build and tests
Response mentions "created", "added", "implemented" review the changes
git diff or git status was run commit the changes
Response mentions "installed" or "install" verify installation
Fallback what's next?

If you backspace to an empty prompt, the suggestion reappears. Suggestions matching your last input are suppressed to avoid repetition (e.g., after you say "verify installation", it won't suggest it again).

/context output

The /context command shows a summary of the current context window state:

Context Window Usage
────────────────────────────────────────
  Model:              claude-opus-5-5
  Context window:     1,000,000 tokens
  Output reserve:     8,192 tokens
  Usable budget:      991,808 tokens
────────────────────────────────────────
  System prompt:      3,412 tokens
  Conversation:       1,280 tokens (6 messages)
  Total used:         4,692 / 991,808 tokens (0.5%)
────────────────────────────────────────
  Compaction at:      85% usage

(Above example uses Opus 4.8's 1M context. Older / 200k-tier models like Opus 4.6 or Haiku 4.5 will show 200,000 tokens and a 191,808 usable budget instead — see Budget Defaults for the per-model table.)

When usage exceeds the compaction threshold, a warning is shown:

  ⚠ Compaction will trigger on next message

Examples

# Show version and help
nsys-cortex --version
nsys-cortex --help

# Neuron lifecycle
nsys-cortex create my-neuron
nsys-cortex exec my-neuron "ls -la"
nsys-cortex exec my-neuron "console.log(1+1)" -l node
nsys-cortex install my-neuron lodash --via npm
nsys-cortex destroy my-neuron --wipe

# Persistence test
nsys-cortex test-persistence my-neuron

# Interactive AI chat (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)
nsys-cortex chat my-neuron
nsys-cortex chat my-neuron --model claude-opus-5-5

# Non-interactive chat
nsys-cortex chat my-neuron -p "What is 2+2?"
nsys-cortex chat my-neuron -p "List files" --json
echo "Hello" | nsys-cortex chat my-neuron

# During an interactive chat session:
#   You> /help             — list all commands
#   You> /model gpt-6.1-sol — switch model mid-session
#   You> /cost             — cumulative token usage
#   You> /save             — export transcript as markdown
#   You> /context          — show token usage
#   You> /clear            — reset conversation
#   You> /exit             — quit chat