New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@omega3_0/nanoagent

Package Overview
Dependencies
Maintainers
1
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@omega3_0/nanoagent

Ultra-lightweight, scalable CLI/TUI coding agent optimized for tiny local models & remote runtimes

latest
Source
npmnpm
Version
2.7.22
Version published
Maintainers
1
Created
Source

⚡ NanoAgent (nanoagent)

  _  _                 _                    _
 | \| |__ _ _ _  ___  /_\  __ _ ___ _ _ | |_
 | .` / _` | ' \/ _ \/ _ \/ _` / -_) ' \|  _|
 |_|\_\__,_|_||_\___/_/ \_\__, \___|_||_|\__|
                          |___/
      ⚡ NanoAgent — Tiny Models, Scalable Intelligence ⚡

Current release: 2.7.22 (@omega3_0/nanoagent) — secret redaction preserves ordinary token expressions and source syntax in file reads.

An ultra-lightweight CLI/TUI coding agent built for tiny local models (2B–8B, especially Qwen 2.5/3.5) that also scales to supported cloud APIs via its OpenAI-compatible integrations (OpenAI, OpenRouter, DashScope/Model Studio, Azure AI Foundry, Kimi, and similar providers). Run locally, think globally.

⚠️ Work in progress

NanoAgent is actively developed and still a WIP. The core loop, TUI, and install paths work, but this is not a finished product.

If you clone this repo or install the npm / GitHub release packages, expect:

  • Some features to be experimental, incomplete, or buggy (sub-agents, memory graph, MCP, skills, native packages, and small-model edge cases in particular)
  • Config, slash commands, and behavior to change between versions
  • Occasional rough edges on Windows and with local runtimes (LM Studio / Ollama)

Please file issues at github.com/leeno7786-coder/nanoagent/issues. Do not treat this as production-ready without testing it on your own workload.

Key features

  • Single install state root — install-global files live under NANOAGENT_ROOT (config/, skills/, tools/, sessions/, workspace/, logs/). Project sessions and rollback history live under the selected workspace's .nanoagent/ directory. One source of truth, no homedir/cwd/legacy fallback search.
  • One boot script — nanoagent / nanogent / nano-agent all dispatch scripts/run-nanoagent.mjs. That script resolves the package separately from writable state, creates the state layout, sets NANOAGENT_ROOT, and chdirs the child. Nothing else boots the agent.
  • Launch from anywhere — nanoagent, nanogent, nano-agent, or npx @omega3_0/nanoagent
  • Tiny-model first — compact prompts, context auto-compact (default 80% of the live window), and small-model tool-call resilience
  • OpenTUI dashboard — streaming chat, tool diffs, todos, skills overlay, connect overlay, six themes, and a Ctrl+P command palette
  • Message queue — type while the agent runs; messages enqueue (up to 20), drain automatically when idle, edit with ↑, persist across sessions; /queue to list, remove, or clear
  • Ask-user questions — the question tool is a first-class clarifying picker: when the request is ambiguous (stack, features, approach), the agent calls it and a TUI overlay collects the answer. It is not an error path and is not tied to consecutive tool rounds.
  • Permissions — read_only / ask / allow_edits / always_allow, plus Shift+Tab to cycle in the TUI
  • Remote sub-agents - explore_subagent workers against a pool you configure in /settings → Sub-agents (endpoint + model + parallel lanes); no model is assumed for you
  • MCP — local stdio or remote HTTP servers (/mcp, /mcp-add, /mcp-remove); only the canonical global config is trusted by default
  • Skills — bundled + user skills under NANOAGENT_ROOT/skills/, auto-load on triggers, /skills overlay (F8)
  • Memory graph — /graph build|stats|report for codebase structure
  • Security defaults — command checks, workspace path sandboxing, secret redaction (see SECURITY.md)
  • Headless CLI — run, doctor, models, todo for scripts and CI

Install

Option 1: Global npm (quick start)

npm install -g @omega3_0/nanoagent
npm install -g @omega3_0/nanoagent@latest   # update

Requires Node ≥ 18 (for npm itself). The Bun runtime ships inside the package — npm pulls the matching @oven/* platform binary automatically, so there is no separate Bun install and no network fetch beyond the registry. If the bundled binary can't run, plain Node ≥ 18 executes everything headless.

Option 2: Native packages (bundled runtime, no Node required)

Linux (Ubuntu/Debian amd64) — self-contained .deb with bundled Node 20:

# Download the latest nanoagent_*_amd64.deb from GitHub Releases, then:
sudo apt install ./nanoagent_*_amd64.deb
nanogent --help

Windows (x64) — portable zip with bundled Node 20 (no installer required):

Download nanoagent_*_win_x64.zip from GitHub Releases
Extract → run nanogent.cmd  (or nanoagent.cmd)

Latest assets: github.com/leeno7786-coder/nanoagent/releases

Build both locally from this repo:

bun install --frozen-lockfile   # or: npm install
bun run package:deb             # → dist-packages/nanoagent_<version>_amd64.deb
bun run package:win             # → dist-packages/nanoagent_<version>_win_x64.zip
# or: bun run package:native    # both

Remove Linux package: sudo apt remove nanoagent

Option 3: npx (no install)

npx @omega3_0/nanoagent

Option 4: Build from source

NanoAgent is MIT-licensed open source — cloning and building locally gives you the same thing the packages ship:

git clone https://github.com/leeno7786-coder/nanoagent.git
cd nanoagent
bun install --frozen-lockfile   # or: npm install
sudo ln -sfn "$(pwd)/scripts/run-nanoagent.mjs" /usr/local/bin/nanogent
sudo ln -sfn "$(pwd)/scripts/run-nanoagent.mjs" /usr/local/bin/nanoagent

scripts/run-nanoagent.mjs is the only entry point. In a source checkout it runs src/main.ts via bun (same path as bun run start); packaged .deb / Windows zip / npm installs have no src/ and use compiled dist/main.js from the package directory. The launcher creates the canonical layout under the writable NANOAGENT_ROOT on first run. If the installed package directory is read-only, it uses a writable per-user state directory; set NANOAGENT_ROOT to override state without changing where the package entry is found.

Release automation

Releases are driven by annotated v* tags. After committing a version bump, push both main and the matching tag; GitHub Actions validates that the tag version matches package.json, runs deterministic install + CI checks, publishes the npm package through trusted OIDC publishing, builds the Linux .deb and Windows zip, and creates the GitHub Release:

npm version patch --no-git-tag-version
git add package.json bun.lock README.md .github/workflows/release.yml .github/workflows/ci.yml
git commit -m "release: v$(node -p \"require('./package.json').version\")"
git tag -a "v$(node -p \"require('./package.json').version\")" -m "Release v$(node -p \"require('./package.json').version\")"
git push origin main --follow-tags

Launch

nanoagent          # interactive TUI in the current directory
nanogent           # same binary
nanoagent tui      # force TUI
nanoagent --sessions              # list conversation hashes for this workspace
nanoagent --resume a1b2c3d4       # load that conversation on boot
nanoagent a1b2c3d4                # same — a 4–16 hex token is treated as a resume hash

The launcher prints the resolved layout on first run, something like:

NanoAgent root : /home/user/.local/state/nanoagent
config/         : /home/user/.local/state/nanoagent/config
skills/         : /home/user/.local/state/nanoagent/skills
tools/          : /home/user/.local/state/nanoagent/tools
sessions/       : /home/user/.local/state/nanoagent/sessions
workspace/      : /home/user/.local/state/nanoagent/workspace
logs/           : /home/user/.local/state/nanoagent/logs

Set NANOAGENT_ROOT before invoking to point at a different writable state location (portable, vendored, per-project). The launcher still resolves the package and dist/main.js beside the boot script. There is no other path-resolution knob.

CLI commands

CommandWhat it does
nanoagent / nanoagent tuiFull-screen OpenTUI session
nanoagent --resume HASHOpen TUI on a saved conversation hash
nanoagent --sessionsList hashes in <workspace>/.nanoagent/sessions
nanoagent run --prompt "…"One headless task
nanoagent modelsList models from the configured runtime
nanoagent doctorConfig + runtime health check
nanoagent todoCLI todo list (add, list, done, delete, clear)

run flags: --prompt / --stdin, --workspace, --model, --base-url, --profile, --json, --quiet, --verbose, --yes (auto-approve permissions), --permission-mode <read_only\|ask\|allow_edits\|always_allow>.

  • Model: Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF (or another Qwen 3.5 2B–8B)
  • Runtime: LM Studio at http://127.0.0.1:1234/v1, Lemonade Server at http://127.0.0.1:13305/api/v1, or Ollama at http://127.0.0.1:11434/v1

Sub-agents are opt-in and fully user-configured — nothing is auto-detected. Open /settings → Sub-agents and set:

  • Enabled — on/off
  • Endpoint — base URL of any OpenAI-compatible server (http://127.0.0.1:1234/v1 for LM Studio, https://openrouter.ai/api/v1 for cloud, …)
  • Fetch models — queries that endpoint and lists what it serves (LM Studio reports loaded state and context window); then cycle the Model row to pick one, or type any id by hand
  • API key — only for endpoints that need one
  • Parallel lanes — workers running simultaneously through that one model, 1–16. Match it to the endpoint's prediction slots and your memory: more lanes on one local model costs more VRAM and each lane gets slower.
  • Avenues per turn — how many separate investigations the agent may dispatch in one message. Deliberately independent of lanes: with 4 avenues and 1 lane it still dispatches four differently-prompted workers, they just queue, so you get breadth without the memory cost.

explore_subagent only appears once an endpoint and a model are set. Give it a paths list to scope the work — each named path is expanded into a real listing placed ahead of the workspace tree, so a deep directory can never be truncated out of reach:

{ "prompt": "How does the retry/backoff path work? Cite line numbers.",
  "paths": ["src/llm/rate-limit.ts", "src/agent/run.ts"] }

Workers run synchronously and return structured findings. A worker that returns a report without calling a single tool is flagged UNGROUNDED in the result — treat its claims as unverified.

To tune the worker itself, add subagents.instructions to the config, or drop prose in <NANOAGENT_ROOT>/config/subagent-instructions.md. Both are appended to the built-in prompt, which always keeps the read-first and grounding rules.

The same values can be written by hand as a subagents block (which also supports several endpoints):

"subagents": {
  "enabled": true,
  "endpoints": [
    { "name": "local", "baseURL": "http://127.0.0.1:1234/v1", "model": "<id from Fetch models>", "concurrency": 4 }
  ]
}

First-run: type /connect in the TUI to pick a provider (Local first, then Cloud), enter an API key if needed, and choose a model.

Cloud providers include OpenAI, OpenRouter, Azure AI Foundry (per-resource URL), Alibaba Cloud Model Studio / DashScope (intl, China, and Coding Plan), Kimi Code, Moonshot, DeepSeek, Groq, xAI, Together, Fireworks, Cerebras, MiniMax, NVIDIA NIM, GMI Cloud, Hugging Face, Gemini (OpenAI-compat), and others. Local extras include Lemonade Server, Foundry Local, SGLang, MLX, KoboldCpp, and Docker Model Runner. All of these speak OpenAI Chat Completions — no extra SDKs.

Configuration

The global config lives at exactly one path: $NANOAGENT_ROOT/config/nanogent.json. There is no other global config location — not ~/.nanogent.json, not ~/.nanoagent.json, not ~/.nanogent/config.json, not ~/.qwen-agent.json. Project overrides live at <workspace>/nanogent.json and are only consulted when --workspace is passed explicitly (the workspace defaults to $NANOAGENT_ROOT/workspace, which is intentionally separate from the harness so the canonical root stays clean).

Project config files are untrusted. They may provide ordinary model/runtime preferences, but cannot change the workspace, endpoint or key, permissions, security settings, system prompt, profiles, failovers, sub-agent pool, MCP trust metadata, or MCP auto-connect policy. Put those settings in the canonical state-root config or pass an explicit trusted config path.

{
  "model": "Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF",
  "baseURL": "http://127.0.0.1:1234/v1",
  "workspace": "./",
  "permissionMode": "ask",
  "contextCompactThreshold": 0.8,
  "maxRequestsPerMinute": 20,
  "maxConcurrentLlmRequests": 2,
  "maxTokensPerMinute": 200000,
  "maxToolResultTokens": 8000,
  "maxToolCallArgumentTokens": 4000,
  "effort": "low",
  "profiles": {
    "local": {
      "model": "Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF",
      "baseURL": "http://127.0.0.1:1234/v1",
      "maxToolResultTokens": 0
    },
    "cloud": {
      "model": "openrouter/free",
      "baseURL": "https://openrouter.ai/api/v1",
      "provider": "openrouter",
      "maxRequestsPerMinute": 20,
      "maxConcurrentLlmRequests": 2,
      "maxToolResultTokens": 8000
    }
  },
  "fallbacks": [
    {
      "model": "openrouter/free",
      "baseURL": "https://openrouter.ai/api/v1",
      "provider": "openrouter"
    }
  ],
  "subAgentEnabled": true,
  "maxBackgroundSubAgents": 4,
  "securityEnabled": true,
  "securityValidateCommands": true,
  "securitySanitizeOutput": true
}

Paid/cloud pacing (local runtimes stay unlimited): maxRequestsPerMinute and maxConcurrentLlmRequests in config, or:

QWEN_MAX_REQUESTS_PER_MINUTE=20 QWEN_MAX_CONCURRENT_LLM=2 nanogent run --prompt "status" --workspace .
QWEN_MAX_TOKENS_PER_MINUTE=200000 QWEN_MAX_TOOL_RESULT_TOKENS=8000 nanogent doctor --json
nanogent doctor --json

Catalog defaults when unset: OpenRouter 20 RPM / 2 in-flight, Groq 30/2, Cerebras 30/2, Hugging Face 15/1. 0 is unlimited. File/env always win over catalog. rateLimitMs is an optional extra pause between agent iterations (default 0; do not use it for cloud 429s). QWEN_MAX_RPM is an alias for QWEN_MAX_REQUESTS_PER_MINUTE.

Optional TPM (maxTokensPerMinute / QWEN_MAX_TOKENS_PER_MINUTE, alias QWEN_MAX_TPM) is off unless you set it — there is no catalog TPM default. Cloud tool results are capped at 8000 tokens by default after secrets sanitize (maxToolResultTokens / QWEN_MAX_TOOL_RESULT_TOKENS; 0 = off; local stays uncapped unless you set a value). Session $ estimates use OpenRouter catalog prices when available, or promptPricePerMillion / completionPricePerMillion (QWEN_PROMPT_PRICE_PER_MILLION, QWEN_COMPLETION_PRICE_PER_MILLION). Prices are never invented; /usage and the status bar show tokens only when rates are unknown.

Context windows come from the live runtime when the catalog reports them: LM Studio loaded instance, OpenRouter context_length, or a cached GET /models on other OpenAI-compatible clouds (context_length / max_model_len / max_context_length). Missing fields stay on the existing heuristic — NanoAgent never invents a smaller window. Catalog capability flags (supportsTools, supportsThinking, supportsPromptCache) are opt-in only when the provider is explicit; unknown keeps today's request shape (enable_thinking for qwen* / bonsai*, tools always sent). Cloud endpoints that advertise prompt cache get a stable prompt_cache_key (workspace + model). Opt out with \"promptCache\": false or QWEN_PROMPT_CACHE=0. Local providers skip cache hints. /config show and nanogent doctor --json include the resolved context source and known flags when set.

Context accounting and compaction

The TUI context indicator shows the live prompt fill, not the cumulative session total. When a provider reports prompt_tokens, NanoAgent uses that value as a baseline and tracks local message growth on top of it. It also learns tool-schema and chat-template overhead, so large tool sets are not silently ignored. Flat or stale provider reports cannot freeze the gauge.

Automatic compaction triggers only when live prompt fill reaches 80% of the model's loaded context window (LM Studio instance context, OpenRouter context_length, or the catalog heuristic — one gauge, not session Σ). The leftover ~20% is spent on a summary inference (no tools): the model writes a dense handoff of the conversation, history is wiped to the system prompt + original task + that summary, and the next turn continues from the clean window. /compact runs the same path on demand. The status bar's context value is the source of truth.

Failover is explicit only — NanoAgent never invents a cloud backup. After LLM retries are exhausted, a 429 / 502 / 503 / 504, timeout, or connection error retries the same turn on the next fallbacks[] entry (or QWEN_FALLBACK_MODEL + optional QWEN_FALLBACK_BASE_URL / QWEN_FALLBACK_PROVIDER when the file omits fallbacks). File wins over env; invalid env is logged and ignored. Auth failures (401/403), bad requests (400), and user abort do not fail over. Each fallback is tried once per main-agent turn, and once per explore_subagent worker run. The live session or that worker's in-memory client switches — not $NANOAGENT_ROOT/config/nanogent.json, and not the shared pool default for other workers. API keys are resolved per fallback provider — the primary key is never sent to a different provider.

Profiles are named snapshots in profiles. /profile lists them; /profile cloud applies cloud to the live session (rebuilds the LLM client). Persist with /profile cloud --global. Headless: nanogent run --profile local --prompt "status". Do not hardcode a paid model id; put your OpenRouter/DashScope model in the cloud snapshot.

In the TUI:

  • /config — open the live config overlay (writes $NANOAGENT_ROOT/config/nanogent.json immediately)
  • /config show — print the config summary in chat
  • /config set model <name> — project-local
  • /config set baseURL http://127.0.0.1:1234/v1 --global — machine-wide
  • /config set maxRequestsPerMinute 20
  • /config set maxConcurrentLlmRequests 2
  • /config set maxTokensPerMinute 200000
  • /config set maxToolResultTokens 8000
  • /config set promptCache false
  • /config reload or /reload — reload from disk
  • /set <key> <val> — same as /config set
  • /profile / /profile list — named snapshots
  • /profile <name> — apply live (--global / --local to persist)
  • /settings — alias for /config
  • /effort [none|low|medium|high|extra-high] — show or set thinking effort

/config, /settings, and /effort write $NANOAGENT_ROOT/config/nanogent.json immediately.

Skills

All skills — bundled and user — live at $NANOAGENT_ROOT/skills/. There is no <cwd>/skills, no ~/.agents/skills, no ~/.claude/skills lookup, and no legacy ~/.qwen-agent-tui/skills fallback. Bundled markdown skills (SKILL.md with YAML frontmatter) ship in the package and the launcher installs them to the canonical skills dir on first run. User skills (*.json or <name>/SKILL.md) dropped into the same directory are auto-enabled.

Toggling:

  • /skills (F8) — overlay listing every skill with its enabled state; toggle inline
  • /skill <name> — load a skill into the current session
  • /skill-load <name> / /unload <name> — explicit load/unload
  • enabled: false in the skill's frontmatter or config/skill-config.json keeps it dormant without deleting it

Triggers in the frontmatter or Use when: ... clauses in the description auto-load skills on matching user prompts.

Working tree & rollback

Tools edit the directory you pointed at directly. cfg.workspace is your project; read_file, write_file, edit_file, etc. all read and write inside it. Rollback is the safety net, and it costs nothing until the model actually changes something:

  • Nothing is captured at boot. No baseline copy, no tree walk, no whole-workspace watcher — startup time does not depend on project size, so launching in a large repo or your home directory is as fast as in an empty folder.
  • Write/edit tools save the file right before they change it. write_file, edit_file, and edit_file_lines store the file's current content (or the fact that it did not exist yet) and append a journal line. Only files the model touches are ever stored.
  • Reads are remembered. When the model reads a file, its content and size/mtime are kept. After every shell command only those files are re-checked (a stat each, not a scan), so a file the model read and a command then changed is restorable — even outside git.
  • Shell commands inside a git repo are covered by git. Before execute_command / run_command / install_dependencies, git stash create records the working state (it touches neither your index nor your files); afterwards, whatever the command changed, created, or deleted is journaled with its pre-command content. Outside git, shell edits to files the model never read are not captured — /changes says so.
  • Your own ! commands and your own edits are never journaled — rollback only undoes what the model did.
  • .nanoagent/ is this NanoAgent workspace's own harness state, not an outside project. It is created on the first model change or read, .nanoagent/ is appended to the project's .gitignore (idempotent), and tools hide it from list_dir / search / git_status and block reads/writes/change_workspace into it.
<workspace>/                        # your project (--workspace)
├── .gitignore                      # auto-gains `.nanoagent/` so git ignores harness state
├── .nanoagent/                     # this workspace's NanoAgent harness state (not an outside project)
│   ├── history/
│   │   ├── journal.jsonl           # one line per change / checkpoint / rollback
│   │   └── objects/                # pre-change file contents, content-addressed
│   ├── worktree/                   # latest copy of every file the model touched
│   └── sessions/                   # conversation history for this project
└── … your files                    # tools edit these directly

Each session starts with an automatic checkpoint (written on the first model change). /rollback undoes every model change since then: edited files get their earlier content back, files the model created are deleted. Undone changes are never undone twice, so a second /rollback cannot clobber what you typed afterwards. The model can undo its own work too, with the rollback_changes tool (goes through the normal write permission).

Chat sessions for the project live in .nanoagent/sessions as 8-hex hashes (a1b2c3d4.json). Each run allocates a hash (shown in the TUI banner). Autosave and /save keep writing that same file. List hashes with nanoagent --sessions or /sessions; load one on boot with nanoagent --resume HASH (unique prefixes work, so does /resume HASH). Older install-global sessions under $NANOAGENT_ROOT/sessions that belong to this workspace are copied in once.

Slash commands

CommandWhat it does
/changesFiles the model changed, with the recent journal (marks anything not restorable).
/snapshot [name]Save a named checkpoint (instant — it is a journal marker, not a copy).
/diffsList checkpoints, newest first, with how many changes follow each.
/rollbackUndo every model change this session.
/rollback <checkpoint>Undo model changes made after that checkpoint.
/rollback <file>Restore one file to its content before the model's first change this session.
/sessions /resumeList / resume conversation hashes in .nanoagent/sessions. Boot: nanoagent --resume HASH.

Workspaces used with earlier versions may still have .nanoagent/snapshots/ (including a large init.json); nothing reads it any more and it is safe to delete.

TUI slash commands

CommandDescription
/helpHelp overlay (F1)
/newStart a new session
/clearClear chat (F2); keeps system messages
/compactForce context compaction
/auto <task>Autonomous run (F3 prefills /auto)
/configLive scalar overlay (global persist). /config show prints. /config set writes a file.
/settingsAlias for /config overlay
/effortShow or set thinking effort (none–extra-high; writes global config when set)
/profileList or apply a named model snapshot (--global to persist)
/connectPick runtime, API key, and model
/usageSession input/output tokens and estimated USD (when prices are known)
/doctorHealth check
/modelsLocal/remote models and context
/todo /todosTodo sidebar (F4) / list
/clear-todosRemove all todos
/skillsSkills overlay (F8)
/skill /skill-load /unloadList, load, or unload a skill
/graph build|stats|reportMemory graph
/mcp /mcp-add /mcp-removeMCP servers
/permissionsread_only / ask / allow_edits / always_allow
/queueList queued messages; /queue remove <N> or /queue clear
/cd [path]Change tool workspace
/allow [path]Extra tool path outside the workspace
/theme [name]Switch theme — dark, light, warmDark, coolDark, black (OLED), highContrast (F9 cycles)
/save /load /sessions /resume /renameSession persistence (per-project .nanoagent/sessions)
/changesFiles the model changed this session; /rollback undoes them
/delete-sessionDelete a saved session
/exportExport chat to markdown
/copyCopy selected message
/set <key> <val>Shorthand for /config set
/reloadReload config, skills, and runtime metadata
/exitQuit and auto-save (F10)

Shortcuts

KeyAction
F1Help
F2Clear chat
F3Prefill /auto
F4Todo sidebar
F5Save session
F6Load session
F8Skills overlay
F9Cycle theme
F10Exit
Ctrl+PCommand palette (themes, overlays, session actions)
Shift+TabCycle permission mode
Shift+EnterMulti-line input
Ctrl+↑/↓Select message
Ctrl+CCopy selected message
Ctrl+DAbort current run
WheelScroll the chat
PgUp/PgDn · Shift+↑/↓Scroll the chat (keyboard)
Shift+dragSelect text (bypasses app mouse capture), then Ctrl+Shift+C to copy
Right-click / Ctrl+V / Ctrl+Shift+VPaste — chat keeps line breaks, key fields collapse to one line

Headless examples

nanoagent run --prompt "Refactor index.ts to use async/await" --workspace .
cat task.txt | nanoagent run --stdin --workspace . --quiet
nanoagent run --prompt "check test coverage" --json
nanoagent run -p "run tests and fix failures" -y --permission-mode allow_edits
nanoagent run --profile local --prompt "status" --workspace .
nanoagent doctor --json
nanoagent models --base-url http://127.0.0.1:1234/v1
nanoagent todo add "Write the README"
nanoagent todo list

Security

Enabled by default:

  • Command validation — structural validator (empty check + your custom allow/block lists); the PermissionManager policy gate is the safety net
  • Workspace sandboxing — tools stay in the workspace unless you /allow a path
  • Output sanitization — redacts keys and tokens from tool output
  • Untrusted project configs — a cloned repo cannot auto-connect its own MCP servers or override trust via a workspace .env. The MCP trust check is strictly against $NANOAGENT_ROOT/config/nanogent.json (or an explicitly-passed config path); workspace nanogent.json MCP servers are tracked in mcpUntrusted and blocked from auto-connect.
  • Trust-sensitive env — NANOGENT_TRUST_PROJECT_MCP, QWEN_SECURITY_*, QWEN_BASE_URL, REMOTE_LMSTUDIO_URL, AZURE_OPENAI_ENDPOINT, HF_TOKEN, QWEN_FALLBACK_*, and *_API_KEY are honored only from the real process environment or $NANOAGENT_ROOT/config/.env (the canonical getApiKey() source). Project .env files cannot inject them.

Full details: SECURITY.md. These guards are still evolving with the rest of the project.

Project layout

scripts/
└── run-nanoagent.mjs   # The single boot script (resolves package/state roots, creates the layout, sets env, chdirs, spawns bun or node)

src/
├── main.ts              # Entry point (requires NANOAGENT_ROOT, fails fast otherwise)
├── agent.ts             # AgentCore re-export
├── agent/               # Core state machine: core.ts, run.ts, early-stop.ts
├── agent-messages.ts    # LLM payload, compaction, system-base cache
├── agent-lifecycle.ts   # Init, reconfigure, shutdown
├── agent-subagents.ts   # Sub-agent dispatch wiring
├── agent-todos.ts       # Todo tracking
├── agent-tools.ts       # Agent-facing tool wiring
├── agent-utils.ts       # Shared agent helpers
├── agent-tools/         # Built-in tools exposed to the agent (execute.ts, utils.ts)
├── config/              # defaults.ts, load.ts, validate.ts, profiles.ts, effort.ts, api-keys.ts, paths.ts
├── llm/                 # client.ts, chat.ts, stream.ts, request.ts, rate-limit.ts, failover.ts, cost.ts, tool-result-budget.ts, context.ts
├── context/             # ContextManager (fill, auto-compact)
├── tools/               # file-tools, exec-tools, git-tools, search-tools, graph-tools, mcp-manage, misc, registry
├── tools/file-tools/    # read.ts, write.ts, navigate.ts
├── subagents/           # Pool resolution, dispatch, and worker/ subfolder (loop, scheduler, failover, tool-runner, context)
├── providers/           # catalog.ts, lookup.ts, runtime.ts, qwen-models.ts, index.ts
├── security/            # permissions.ts, patterns.ts, index.ts
├── graph/               # MemoryGraph.ts + tools.ts (build/stats/report)
├── mcp/                 # MCP client (index.ts)
├── skills.ts            # Skill loader (reads only $NANOAGENT_ROOT/skills/)
├── skill-manager.ts     # Skill lifecycle
├── store.ts             # Session persistence (project `.nanoagent/sessions`, global fallback)
├── workspace-history.ts # Touched-file history: pre-change capture, checkpoints, rollback
├── storage.ts           # Disk I/O for sessions
├── lib/                 # Shared utilities (file-diff.ts, etc.)
├── cli/                 # run.ts, doctor.ts, models.ts, todo.ts, help.ts, reports.ts
├── opentui/             # app.tsx, chat-screen.tsx, overlays, slash-commands/, status-bar, connect-overlay
├── types.ts             # Shared TypeScript interfaces
└── *.test.ts            # Colocated bun:test files

NANOAGENT_ROOT is the writable install-global state root resolved at startup (no fallback chain):

NANOAGENT_ROOT/
├── config/      nanogent.json · .env · skill-config.json · todos.json · input-history.json
├── skills/      bundled SKILL.md + user skills
├── tools/       bundled/managed tools
├── sessions/    chat session stores
├── workspace/   default agent workspace (separate from the harness)
└── logs/        stderr.log · crash.log · last-run.json

Changelog

2.7.22 — Source-preserving secret redaction

  • File reads preserve streaming code. Token event comparisons, calls, equality checks, and type annotations no longer become misleading [REDACTED] fragments.
  • Credential literals remain redacted. Quotes, separators, line breaks, and JSON structure survive sanitization, including single-file and batch reads.
  • Regression coverage checks source preservation, credential redaction, escaped quotes, URL query values, and repeated sanitization.

2.7.16 — Qwen tool-call template fix

  • Batched tool rounds no longer break Qwen's Jinja template. Qwen 2.5/3.x and Bonsai raise Tool message must be responding to a previous tool call. unless a tool message is immediately preceded by an assistant. The batched shape assistant(tool_calls=[a,b]) tool(a) tool(b) is rejected on the second result, so a single multi-tool round failed the whole request — and the system prompt tells the model to batch independent tools every turn. Each result is now re-interleaved behind its own single-call assistant turn, which renders identically and satisfies the template.
  • Applies to sub-agents too. The worker history is normalized before every streamChat, so explore_subagent against local Qwen 2B models gets the same fix.
  • Self-healing history. Tool results orphaned by compaction or session edits are dropped, and tool calls a stopped run never answered are stripped, so neither can poison the next request. Internal history is untouched — only the payload sent to the model changes.

2.7.15 — Touched-file rollback, no boot capture

  • Startup no longer captures the workspace. The init.json baseline, the boot-time tree walk, and the recursive watcher are gone, so launching in a large project (or ~) no longer hangs before the TUI appears.
  • Rollback covers exactly what the model changed. Write/edit tools save a file right before changing it; reads are remembered so shell changes to read files are restorable; shell commands in git repos are captured through git stash create. /rollback undoes the session, /rollback <checkpoint|file> undoes part of it.
  • rollback_changes tool lets the model undo its own edits.
  • /snapshot is now an instant checkpoint marker instead of a full-tree copy.
  • The .deb keeps its linux-x64 Bun runtime, so the TUI starts from the Debian package (previously it could only run headless).

2.7.6 — Question overlay for ambiguous requests

  • question is a first-class clarifying picker. Ambiguous product choices (stack, features, constraints) go through the TUI overlay, not chat A/B/C lists. It is not an error path and is not tied to consecutive tool rounds.
  • Prompts tell both small and large models to call question instead of listing options in prose.
  • Fallback: if the model still dumps a lettered quiz in chat, the TUI harness promotes that text into a real question call so the overlay still opens. Headless nanogent run skips promotion (no overlay).

2.7.5 — 80% wipe-clean compaction and write_file JSON repair

  • One context gauge. Auto-compact only when live prompt fill reaches 80% of the model's loaded window. Leftover ~20% runs a no-tools summary inference; history is wiped to system prompt + original task + that handoff. Overflow / empty-length recovery does not compact below 80%. /compact is the same path on demand.
  • write_file JSON. Streaming argument snapshots are merged instead of concatenated; raw newlines and quotes in content are repaired so missing-content writes stop firing.

2.7.4 — Scannable chat diffs

  • Per-file Git Diff labels. Each patch shows its path and +x −y instead of a mashed hunk blob.
  • Quieter tool rows. Git Status / Git Diff hide a target of .; list_dir and git_status show a short name preview.
  • Assistant markdown. Pipe tables render as aligned columns; short reviews are not chopped with a truncation marker.

2.7.3 — Complete diffs and file reads

  • git_diff includes the whole working tree. It is git diff HEAD (staged + unstaged) plus untracked files as new-file diffs, so it matches git_status. The TUI renders each file and summarizes N files · +x · -y instead of looking like a one-hunk excerpt.
  • read_file is no longer a silent 200-line clip. Large/cloud models get up to 2000 lines by default; ≤8B stays at 100. truncated is set only when content was actually cut, with next_start_line so a follow-up is a different call.
  • Cloud tool-result cap no longer says “re-read a narrower range” (that made the model retry the same tool). Oversized diffs drop whole trailing files, not a mid-hunk slice.
  • Recovery UI. Duplicate-tool / stuck-loop / overflow-retry notices use notice-recovery-* and stay off the main chat panel.

2.7.2 — Treat .nanoagent/ as harness state

  • .nanoagent/ is this workspace's harness state. Auto-added to the project .gitignore, hidden from discovery/git tools, blocked as a file-access path (including change_workspace), and named in the system prompt as part of this NanoAgent run — not an outside project folder.

2.7.1 — Release workflow hardening

  • Safer tag-driven releases. The release workflow now verifies v* tag version = package.json version before publish.
  • Deterministic validation before npm publish. Tag releases run bun install --frozen-lockfile and npm run ci before packing/publishing.
  • Trusted npm publish + native artifacts preserved. npm publish uses GitHub OIDC trusted publishing and still attaches the npm tarball, Linux .deb, and Windows zip to the GitHub Release.

2.7.0 — Live worktree history and per-project sessions

  • Automatic file history. Opening a workspace still takes the init.json baseline. After that, write/edit tools, execute_command / git_commit, and a recursive file watcher copy every touched file into <workspace>/.nanoagent/worktree, save the pre-edit original once under .nanoagent/history/originals, and append .nanoagent/history/journal.jsonl. Tools still edit the real project — this is a copy, not the old working-tree redirect.
  • /changes lists files recorded this run. /snapshot remains an optional named checkpoint. /rollback still restores the init baseline or a named snapshot.
  • Per-project sessions. Conversations are stored as 8-hex hashes in <workspace>/.nanoagent/sessions. /save and autosave keep the live hash; /sessions and /resume HASH (unique prefix ok) load them. On boot: nanoagent --resume HASH, nanoagent resume HASH, a bare hex token, or nanoagent --sessions to list. Matching older sessions from $NANOAGENT_ROOT/sessions are copied in once. API keys and MCP config are still omitted from session JSON.
  • Tests: worktree journal (originals, create/update/delete, skip lists), project-local session save/migrate, write_file + execute_command hooks, /changes.

2.6.5 — Snapshot capture survives unreadable caches

  • /snapshot and the init baseline no longer die on EPERM. Capture skips VCS, dependency, and cache directories (same list as tool search: .git, node_modules, .pytest_cache, …) and continues past directories that cannot be scanned instead of aborting the whole snapshot.
  • /rollback does not delete skipped trees. Restore uses the same walk, so caches and node_modules are left alone.
  • Init//cd now warn if a baseline still cannot be taken (was debug-only).
  • Tests: 3 new snapshot-walk cases; listSnapshots newest-first order is pinned so same-millisecond captures cannot flake. Suite: 995 pass.

2.6.4 — Windows/Bun stdin race fix

  • TUI stdin race on Windows/Bun — yield to the event loop immediately after createCliRenderer so keyboard input is not lost on startup.

2.6.3 — Queue shutdown persistence and overflow fixes

  • Queue data loss on shutdown — shutdownAgent passes messageQueue through to autoSaveSession so headless/CLI exit paths do not drop queued messages.
  • Overflow escalation — non-streaming overflow recovery passes overflowRetries to forceCompactContext; first overflow targets 10% of the window instead of 20%.
  • reloadAgentFromDisk data loss — /config reload preserves all config keys via spread + extractPreservedFields() instead of a cherry-picked field list.
  • Settings overlay — MCP config is cached with a revision counter; model catalog index prefers providerId + modelId matches to reduce cross-provider collisions.
  • Queue drain — user notice when a queued message is dropped after 3 retries; dead code removed from /queue remove.
  • Misc — stale explore_subagent error path updated to config/nanogent.json; duplicate maxBackgroundSubAgents removed from advanced settings.

2.6.2 — Queue display and drain race

  • Queue drain race — fix race when draining the message queue after abort or run completion.
  • Queue UI — move the queue indicator above the input bar.

2.6.1 — Message queue

  • Message queue — send messages while the agent is busy; up to 20 queued, auto-drained when idle, and restored when present in persisted sessions (including autosave/shutdown saves).
  • /queue — list, /queue remove <N>, and /queue clear.
  • Edit queued messages — ↑ enters edit mode when the input is empty; save edits in place.
  • Reliability — drain/requeue fixes, retry tracking, nav inversion fix, persistence across restore.

2.6.0 — Interactive question tool

  • question tool — first-class clarifying picker for ambiguous user requests (stack, features, approach). Single/multi-select with optional custom text.
  • Question overlay — TUI overlay with keyboard navigation; Escape cancels; 5-minute timeout. Opens because the model called question, not because of a tool-round check-in or an API error.
  • Wiring — registered for all models. Prompts tell the model to call question instead of listing A/B/C in chat; if it still dumps a lettered quiz, the TUI harness promotes that text into a question call.

2.5.8 — Small-model prompt and tool consistency

  • API-reported prompt_tokens are used as the live context baseline when available; local message growth and tool-schema/chat-template overhead are tracked on top.
  • Flat or stale provider reports cannot freeze the context gauge. Auto-compact at 80% of the loaded window runs a summary inference in the leftover 20%, then continues from system prompt + original task + that handoff.
  • Restored sessions, mutable todo/system messages, compaction summaries, and tool-schema changes stay synchronized with ContextManager accounting.
  • Small-model mode changes now refresh the live prompt and model policy immediately.
  • Remote sub-agent instructions and explore_subagent are advertised only when a pool is configured.
  • Release automation publishes npm and native packages from a pushed v* tag through GitHub Actions.
  • Tests: full suite passes.

2.5.5 — Reasoning-loop root cause: output-cap escalation

  • The repeating-analysis loop is fixed at the root. Thinking models (Qwen/Bonsai ≤8B) were burning the entire 4096-token default output budget mid-thought, so every turn ended finish_reason=length with zero visible content — and the 2.5.3 nudge couldn't help, because the retry truncated at the same point. When a reasoning-only turn ends length, the harness now doubles the session's output cap (up to 32768) before nudging, with a notice showing the raise.
  • Alternating loops terminate. The reasoning-only streak reset on any healthy turn, so a model alternating think-only turns with tool rounds could loop until the iteration cap. A cumulative per-run cap (2× the streak cap) now stops those.
  • Clearer terminal notice when the model never replies: suggests raising maxTokens or switching models.
  • Tests: 2 new regression tests (cap escalation, cumulative-cap termination); suite 931 pass / 0 fail.

2.5.4 — Small-model prompt rework + dead-input fix

  • Small-model system prompt reworked around a "turn contract". Every reply must end with tool calls or a visible answer — never internal thinking only — so thinking-channel models don't burn turns on invisible analysis. Adds act-don't-narrate, keep-moving (act on tool results immediately), chat-style output rules (brief working notes, skimmable final summary), and recovery rules (never repeat an identical failing call).
  • Fix dead chat input on Windows. Mouse capture (restored in 2.5.3 for wheel scrolling) also enabled mouse-movement tracking by default; the flood of motion reports on Windows Terminal could starve the key parser, leaving the chat bar unresponsive. Movement tracking is now off — wheel scroll and click paste keep working.
  • Tests: suite 929 pass / 0 fail.

2.5.3 — Reasoning-only loop fix + scroll restore

  • No more invisible analysis loops. Small thinking models (e.g. Qwen/Bonsai ≤8B) sometimes dump their entire analysis into the reasoning channel and emit no visible reply or tool calls. The harness retried silently with identical history — reasoning turns are stripped from the model payload — so the model deterministically regenerated the same analysis until the cap hit, with the context counter never moving. Each reasoning-only turn now injects a hidden nudge ("respond with content or tool calls") so the retry sees new context, and a visible ↻ Model produced thinking only… (n/max) notice makes the loop transparent.
  • Small models give up sooner. Reasoning-only retries cap at 3 in ≤8B mode (was the default 5); /config set maxReasoningOnlyRounds still overrides.
  • Wheel scrolling restored — mouse capture re-enabled so the main chat window scrolls again.
  • Tests: suite 929 pass / 0 fail.

2.5.2 — Workspace follows the launch directory

  • Tools see your actual project. The launcher chdirs the child into the install root (by design), and the config then defaulted the workspace to the canned <NANOAGENT_ROOT>/workspace — so the agent could only see placeholder files, not the directory you opened nanoagent in. The launcher now captures your launch directory as NANOAGENT_LAUNCH_CWD and the agent's default workspace resolves to it.
  • Precedence: --workspace / QWEN_WORKSPACE → launch directory → install-root workspace (fallback for tests/embedded use). /cd still switches at runtime.
  • Trust boundary kept: a nanogent.json in the launch directory is not read as a config override — opening nanoagent in a cloned repo can't silently reconfigure the agent.
  • Tests: 4 new workspace-precedence tests; suite 929 pass / 0 fail.

2.5.1 — CI green + command surfacing fixes

  • CI pipeline green again — fixed 7 lint errors, prettier drift across src/, and two real bugs the red pipeline was hiding: --help now works without the launcher (the NANOAGENT_ROOT guard ran before help handling), and mcp-manage.ts / store.ts no longer resolve install paths at import time (which crashed any direct bun src/main.ts invocation).
  • Docker image fixed — the build stage now copies scripts/ (the fix-ext build step needs it), and the image ships a canonical NANOAGENT_ROOT layout with bundled skills so the container actually boots.
  • Rollback system is reachable from the UI — /snapshot, /diffs, /rollback (plus /new, /sessions, /resume, /rename, /delete-session, /copy, /clear-todos, /skill-load, /unload) now appear in the / dropdown and F1 help. The Ctrl+P palette gains Snapshot / List snapshots / Rollback actions; rollback prefills /rollback into the input instead of executing, so a stray Enter can't reset the workspace to baseline.
  • Overlay/focus conflict fixed — opening Ctrl+P (or any overlay) while the / dropdown was open left the dropdown capturing keys and broke chat-bar focus afterward. Overlays now suspend the dropdown and own focus; closing restores the chat input.
  • Tests: full suite green locally for the first time — 925 pass / 0 fail (env-dependent tests use the bundled bun or skip cleanly).

2.5.0 — TUI polish pass

A ground-up visual cleanup of the OpenTUI interface, aimed at a quieter, more polished chat surface.

  • Six themes — dark, light, warmDark, coolDark, black (OLED), and highContrast, all built on full semantic tokens (diff backgrounds/signs, code background, accent/on-accent pairs, syntax palette). /theme <name> to pick, F9 to cycle, live preview from the command palette.
  • Ctrl+P command palette — opencode-style floating palette with fuzzy filter over app actions (theme, settings, connect, compact, export, sessions, skills, permissions, exit). Theme switching previews live without closing the palette.
  • Quiet tool rows — muted →/←/$ glyphs, accent-highlighted targets, workspace-relative paths, multi-line commands truncated to one line, no per-message token noise.
  • Full-width tinted diffs with line numbers and colored gutters; code blocks render on a dedicated background; chat text renders inline markdown (`code`, **bold**, ## headings).
  • Inline slash dropdown — / suggestions render directly above the input with accent-bar selection and right-aligned F-key hints, not as a modal.
  • Normal-terminal copy/paste — mouse capture removed entirely (the F7 toggle is gone). Drag-select copy and right-click / Ctrl+Shift+V paste work natively; Ctrl+V / Shift+Insert fall back to clipboard tools. Paste heuristics: the chat input preserves interior line breaks, single-line fields (API keys) collapse to one trimmed line.
  • Width-aware status bar — one compact row that measures itself against the terminal and drops low-priority stats instead of garbling; slim one-line welcome banner.
  • Tests: 153 TUI tests + clipboard heuristic coverage; suite green (2 pre-existing environment failures unrelated to the TUI).

2.4.0 — Snapshots and rollback

Tools edit the user's project directly. cfg.workspace IS the directory the user pointed at; read_file, write_file, edit_file, etc. all read and write inside it. Rollback is the safety net: on first agent init, a baseline snapshot of every file in the workspace is captured at <workspace>/.nanoagent/snapshots/init.json. Named snapshots (/snapshot <name>) record additional checkpoints as diffs against the previous named snapshot, so the store stays small.

  • /snapshot [name] — capture the current workspace state. First snapshot is a full capture; later ones are diffs.
  • /diffs — list every saved snapshot, newest first.
  • /rollback <name> — restore a named snapshot, walking the chain so deletions compose correctly. new.ts added after a snapshot and removed after a later one reverts to "gone" when rolling back to the earlier one — the chain walk reconstructs the file set at that point in time.
  • /rollback (no name) — restore the workspace to the baseline (init.json). The user's edits are lost unless they were /snapshotted first.
  • Startup banner shows the baseline status, so you always know what /rollback will revert to.
  • Files: src/snapshots.ts (capture, list, restore, delete, baseline, restoreBaseline), src/agent-lifecycle.ts (take baseline at init), src/opentui/slash-commands/index.ts (the four commands), src/agent-tools/execute.ts (tools now pass agent.cfg.workspace directly — no more working-tree redirect), src/opentui/app.tsx (banner).
  • Tests: 16 new unit tests in src/snapshots.test.ts (baseline, named snapshots, diff chain, restore, list), 5 new slash-command tests in src/opentui/slash-commands.test.ts. Suite: 921 pass / 2 pre-existing-on-main fail.
  • Removed: the previous "lazy copy into <workspace>/.nanoagent/working-tree/" model — the working tree was the wrong place to put edits. The rollback store lives at <workspace>/.nanoagent/snapshots/ and tools operate on the source directly.

2.3.0 — Single canonical install root

Killed the whole class of "skills / config / sessions disappear depending on cwd" bugs by collapsing every state directory onto one path. One source of truth, no fallback searching, fail fast on duplicates.

  • NANOAGENT_ROOT is the only state directory. Resolved once by scripts/run-nanoagent.mjs and exported to the child. There is no ~/.nanoagent, no ~/.qwen-agent-tui, no <cwd>/.env lookup, no <cwd>/skills lookup, no legacy read fallback. Set NANOAGENT_ROOT=/path/to/install and every other path follows.

  • One boot script. nanoagent / nanogent / nano-agent / npx @omega3_0/nanoagent all dispatch scripts/run-nanoagent.mjs. The launcher creates the layout (config/, skills/, tools/, sessions/, workspace/, logs/) on first run, sets NANOAGENT_ROOT, and chdirs the child so process.cwd() is never an accident. The banner:

    NanoAgent root : /home/user/.local/share/nanoagent
    config/         : /home/user/.local/share/nanoagent/config
    skills/         : /home/user/.local/share/nanoagent/skills
    tools/          : /home/user/.local/share/nanoagent/tools
    sessions/       : /home/user/.local/share/nanoagent/sessions
    workspace/      : /home/user/.local/share/nanoagent/workspace
    logs/           : /home/user/.local/share/nanoagent/logs
    
  • Skills load from exactly one place. $NANOAGENT_ROOT/skills/. Bundled markdown skills ship in the package and are installed to the canonical skills dir on first run. User .json and <name>/SKILL.md skills dropped into the same directory are auto-enabled. No more 5-source fan-out.

  • Sessions, scratchpad, .env, todos, history, MCP, crash logs — all canonical. Each lives at a single absolute path under the install root. loadConfig() no longer scans cwd or home for candidates; the global config is $NANOAGENT_ROOT/config/nanogent.json and project overrides only when --workspace is passed.

  • src/config/paths.ts is the new ground truth. installRoot(), nanoagentPaths(), GLOBAL_CONFIG_FILE(), SKILLS_DIR(), SESSIONS_DIR(), etc. all read NANOAGENT_ROOT and throw on missing subdirs. configDir / legacyConfigDir / configFileCandidates are gone.

  • MCP trust narrows to the canonical global config. Only $NANOAGENT_ROOT/config/nanogent.json (or an explicitly-passed config path) is trusted. Project-local MCP servers are tracked in mcpUntrusted and blocked from auto-connect; this was already the rule for 2.x, but now the "trusted path" list is one entry instead of four.

  • main.ts refuses to run without NANOAGENT_ROOT. Trying to bun src/main.ts directly throws — the launcher is the only supported entry. Removes a class of "why does it work in dev but not in the published package" surprises.

  • Tests rewritten for the new model: 899 pass / 2 fail, both pre-existing on main (the run_command lifecycle test and the launcher-help test need bun on PATH for the test process; nothing the agent owns at runtime).

2.2.6 — Drop the dangerous-pattern blocker, surface disabled skills

Two fixes that unblock agent work:

  • No more dangerous-command blocker. The built-in DANGEROUS_COMMAND_PATTERNS list (src/security/patterns.ts) was rejecting commands that small models had to run, with no path through the ask / always_allow permission flow. SecurityManager.validateCommand is now a structural validator (empty check + your custom blockedCommands / allowedCommands lists); the PermissionManager policy gate (ask / allow_edits / always_allow / read_only) is the sole safety net for shell commands. Review each command when prompted, then choose always_allow for the ones you trust. SECURITY.md synced.
  • Disabled skills are visible in / autocomplete. Project-local skills (./skills/) correctly default to enabled: false (prompt-injection guard), but they were filtered out of the slash-command dropdown entirely, so users thought they didn't exist. getSkillCommands(skills, { includeDisabled }) now surfaces them marked [disabled] and greyed out; toggle via /skills (F8) or /config. Enabled entries still sort first.

2.2.5 — Pre-existing test failures were real bugs

The 7 "flaky" local failures turned out to be two genuine bugs plus a test gap:

  • *GIT_CONFIG* family stripping_* — environments that inject git config via GIT_CONFIG_COUNT/KEY_n/VALUE_n (harnesses, CI) had only the KEY_n members stripped by the sensitive-var filter, and git dies on the partial family ("missing config key"). getSanitizedEnv now treats the family as all-or-nothing.
  • Config precedence — ~/.nanogent.json silently overwrote explicitly-passed options, so an explicit --base-url could be replaced by the saved /connect selection and the catalog would resolve the wrong provider's API key. Explicit options now win.
  • Test hermeticity — scrub lists cover every trusted .env location via the canonical config/.env.

Full suite is green locally for the first time: 909 pass / 0 fail.

2.2.4 — Write/edit tool integrity

Fixes the "models mangle files" complaint, three ways:

  • No more silent argument truncation — tool-call arguments were token-capped (~4000) before execution; for write_file that cut the content field itself, and a broken parse made write_file empty the target file. File-payload tools are exempt from capping; execution always sees full arguments.
  • Refusal instead of corruption — write_file / edit_file / edit_file_lines reject truncated or unparseable arguments with an actionable error instead of writing partial/empty content.
  • Line-number echo stripping — small models copied read_file's NNNN| display prefixes into write/edit payloads; the tools now strip them (only on a strict ≥80%-of-lines, strictly-increasing match) and report line_number_echo_stripped.

2.2.3 — The "ghost crash" fix

The multi-release hunt for a crash that wasn't a crash. Symptom: accepting a permission prompt garbled the display and the process seemed to vanish. Each release added forensics until the evidence named the culprit:

  • 2.1.22 — crash.log for uncaught exceptions/rejections. Result: nothing logged — JS handlers never fired.
  • 2.2.0 — state dir moved to ~/.nanoagent, giving diagnostics a stable home.
  • 2.2.1 — OpenTUI 0.2.1 → 0.5.9 upgrade + last-run.json liveness marker. Result: marker dirty, no exit event — looked like a native-level kill.
  • 2.2.2 — launcher tees child stderr to the logs dir. Breakthrough: the log ended with Received SIGINT, shutting down gracefully... — users were Ctrl+C'ing out of a garbled frame, not experiencing a native panic at all.
  • 2.2.3 — the actual fix: the agent tool loop passed model args verbatim to execute_command, so output mirroring defaulted to ON and child stdout/stderr was written raw into the terminal while the TUI held the alternate screen, corrupting the frame. Mirroring is now suppressed whenever the TUI is active. Also fixed the root of the noise seen in the log: the Windows system prompt claimed "PowerShell" while execute_command actually runs Git Bash, so the model emitted PowerShell one-liners bash rejected with syntax errors.

Also fixed along the way: permission banner [Y]/[A]/[N] row layout under OpenTUI 0.5.9 (2.2.2), /connect provider persistence and OpenRouter key validation (2.1.21).

License

MIT License

Keywords

agent

FAQs

Package last updated on 08 Oct 2026

Related posts