@omega3_0/nanoagent
v2.7.22
Published
Ultra-lightweight, scalable CLI/TUI coding agent optimized for tiny local models & remote runtimes
Downloads
6,877
Maintainers
Readme
⚡ 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-agentall dispatchscripts/run-nanoagent.mjs. That script resolves the package separately from writable state, creates the state layout, setsNANOAGENT_ROOT, and chdirs the child. Nothing else boots the agent. - Launch from anywhere —
nanoagent,nanogent,nano-agent, ornpx @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;
/queueto list, remove, or clear - Ask-user questions — the
questiontool 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_subagentworkers 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,/skillsoverlay (F8) - Memory graph —
/graph build|stats|reportfor codebase structure - Security defaults — command checks, workspace path sandboxing, secret redaction (see SECURITY.md)
- Headless CLI —
run,doctor,models,todofor scripts and CI
Install
Option 1: Global npm (quick start)
npm install -g @omega3_0/nanoagent
npm install -g @omega3_0/nanoagent@latest # updateRequires 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 --helpWindows (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 # bothRemove Linux package: sudo apt remove nanoagent
Option 3: npx (no install)
npx @omega3_0/nanoagentOption 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/nanoagentscripts/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-tagsLaunch
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 hashThe 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/logsSet 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
| Command | What it does |
| ---------------------------------- | -------------------------------------------------------- |
| nanoagent / nanoagent tui | Full-screen OpenTUI session |
| nanoagent --resume HASH | Open TUI on a saved conversation hash |
| nanoagent --sessions | List hashes in <workspace>/.nanoagent/sessions |
| nanoagent run --prompt "…" | One headless task |
| nanoagent models | List models from the configured runtime |
| nanoagent doctor | Config + runtime health check |
| nanoagent todo | CLI 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>.
Recommended local setup
- 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 athttp://127.0.0.1:13305/api/v1, or Ollama athttp://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/v1for LM Studio,https://openrouter.ai/api/v1for 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 --jsonCatalog 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.jsonimmediately)/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 reloador/reload— reload from disk/set <key> <val>— same as/config set/profile//profile list— named snapshots/profile <name>— apply live (--global/--localto 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/unloadenabled: falsein the skill's frontmatter orconfig/skill-config.jsonkeeps 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, andedit_file_linesstore 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 createrecords 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 —/changessays 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 fromlist_dir/ search /git_statusand block reads/writes/change_workspaceinto 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 directlyEach 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
| Command | What it does |
| -------------------------- | ------------------------------------------------------------------------------------------------ |
| /changes | Files 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). |
| /diffs | List checkpoints, newest first, with how many changes follow each. |
| /rollback | Undo 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 /resume | List / 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
| Command | Description |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| /help | Help overlay (F1) |
| /new | Start a new session |
| /clear | Clear chat (F2); keeps system messages |
| /compact | Force context compaction |
| /auto <task> | Autonomous run (F3 prefills /auto) |
| /config | Live scalar overlay (global persist). /config show prints. /config set writes a file. |
| /settings | Alias for /config overlay |
| /effort | Show or set thinking effort (none–extra-high; writes global config when set) |
| /profile | List or apply a named model snapshot (--global to persist) |
| /connect | Pick runtime, API key, and model |
| /usage | Session input/output tokens and estimated USD (when prices are known) |
| /doctor | Health check |
| /models | Local/remote models and context |
| /todo /todos | Todo sidebar (F4) / list |
| /clear-todos | Remove all todos |
| /skills | Skills overlay (F8) |
| /skill /skill-load /unload | List, load, or unload a skill |
| /graph build\|stats\|report | Memory graph |
| /mcp /mcp-add /mcp-remove | MCP servers |
| /permissions | read_only / ask / allow_edits / always_allow |
| /queue | List 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 /rename | Session persistence (per-project .nanoagent/sessions) |
| /changes | Files the model changed this session; /rollback undoes them |
| /delete-session | Delete a saved session |
| /export | Export chat to markdown |
| /copy | Copy selected message |
| /set <key> <val> | Shorthand for /config set |
| /reload | Reload config, skills, and runtime metadata |
| /exit | Quit and auto-save (F10) |
Shortcuts
| Key | Action |
| ----------- | --------------------------------------------------- |
| F1 | Help |
| F2 | Clear chat |
| F3 | Prefill /auto |
| F4 | Todo sidebar |
| F5 | Save session |
| F6 | Load session |
| F8 | Skills overlay |
| F9 | Cycle theme |
| F10 | Exit |
| Ctrl+P | Command palette (themes, overlays, session actions) |
| Shift+Tab | Cycle permission mode |
| Shift+Enter | Multi-line input |
| Ctrl+↑/↓ | Select message |
| Ctrl+C | Copy selected message |
| Ctrl+D | Abort current run |
| Wheel | Scroll the chat |
| PgUp/PgDn · Shift+↑/↓ | Scroll the chat (keyboard) |
| Shift+drag | Select text (bypasses app mouse capture), then Ctrl+Shift+C to copy |
| Right-click / Ctrl+V / Ctrl+Shift+V | Paste — 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 listSecurity
Enabled by default:
- Command validation — structural validator (empty check + your custom allow/block lists); the
PermissionManagerpolicy gate is the safety net - Workspace sandboxing — tools stay in the workspace unless you
/allowa 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); workspacenanogent.jsonMCP servers are tracked inmcpUntrustedand 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_KEYare honored only from the real process environment or$NANOAGENT_ROOT/config/.env(the canonicalgetApiKey()source). Project.envfiles 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 filesNANOAGENT_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.jsonChangelog
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 atoolmessage is immediately preceded by an assistant. The batched shapeassistant(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, soexplore_subagentagainst 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.jsonbaseline, 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./rollbackundoes the session,/rollback <checkpoint|file>undoes part of it. rollback_changestool lets the model undo its own edits./snapshotis now an instant checkpoint marker instead of a full-tree copy.- The
.debkeeps 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
questionis 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
questioninstead 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
questioncall so the overlay still opens. Headlessnanogent runskips 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-
lengthrecovery does not compact below 80%./compactis the same path on demand. write_fileJSON. Streaming argument snapshots are merged instead of concatenated; raw newlines and quotes incontentare 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 −yinstead of a mashed hunk blob. - Quieter tool rows.
Git Status/Git Diffhide a target of.;list_dirandgit_statusshow 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_diffincludes the whole working tree. It isgit diff HEAD(staged + unstaged) plus untracked files as new-file diffs, so it matchesgit_status. The TUI renders each file and summarizesN files · +x · -yinstead of looking like a one-hunk excerpt.read_fileis no longer a silent 200-line clip. Large/cloud models get up to 2000 lines by default; ≤8B stays at 100.truncatedis set only when content was actually cut, withnext_start_lineso 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 (includingchange_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.jsonversion before publish. - Deterministic validation before npm publish. Tag releases run
bun install --frozen-lockfileandnpm run cibefore 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.jsonbaseline. 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. /changeslists files recorded this run./snapshotremains an optional named checkpoint./rollbackstill restores the init baseline or a named snapshot.- Per-project sessions. Conversations are stored as 8-hex hashes in
<workspace>/.nanoagent/sessions./saveand autosave keep the live hash;/sessionsand/resume HASH(unique prefix ok) load them. On boot:nanoagent --resume HASH,nanoagent resume HASH, a bare hex token, ornanoagent --sessionsto list. Matching older sessions from$NANOAGENT_ROOT/sessionsare 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
/snapshotand the init baseline no longer die onEPERM. 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./rollbackdoes not delete skipped trees. Restore uses the same walk, so caches andnode_modulesare left alone.- Init/
/cdnow warn if a baseline still cannot be taken (was debug-only). - Tests: 3 new snapshot-walk cases;
listSnapshotsnewest-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
createCliRendererso keyboard input is not lost on startup.
2.6.3 — Queue shutdown persistence and overflow fixes
- Queue data loss on shutdown —
shutdownAgentpassesmessageQueuethrough toautoSaveSessionso headless/CLI exit paths do not drop queued messages. - Overflow escalation — non-streaming overflow recovery passes
overflowRetriestoforceCompactContext; first overflow targets 10% of the window instead of 20%. reloadAgentFromDiskdata loss —/config reloadpreserves 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+modelIdmatches 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_subagenterror path updated toconfig/nanogent.json; duplicatemaxBackgroundSubAgentsremoved 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
questiontool — 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
questioninstead of listing A/B/C in chat; if it still dumps a lettered quiz, the TUI harness promotes that text into aquestioncall.
2.5.8 — Small-model prompt and tool consistency
- API-reported
prompt_tokensare 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
ContextManageraccounting. - Small-model mode changes now refresh the live prompt and model policy immediately.
- Remote sub-agent instructions and
explore_subagentare 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=lengthwith 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 endslength, 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
maxTokensor 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 maxReasoningOnlyRoundsstill 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 asNANOAGENT_LAUNCH_CWDand the agent's default workspace resolves to it. - Precedence:
--workspace/QWEN_WORKSPACE→ launch directory → install-root workspace (fallback for tests/embedded use)./cdstill switches at runtime. - Trust boundary kept: a
nanogent.jsonin 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:--helpnow works without the launcher (theNANOAGENT_ROOTguard ran before help handling), andmcp-manage.ts/store.tsno longer resolve install paths at import time (which crashed any directbun src/main.tsinvocation). - Docker image fixed — the build stage now copies
scripts/(thefix-extbuild step needs it), and the image ships a canonicalNANOAGENT_ROOTlayout 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/rollbackinto 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), andhighContrast, 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.tsadded 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/snapshottedfirst.- Startup banner shows the baseline status, so you always know what
/rollbackwill 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 passagent.cfg.workspacedirectly — 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 insrc/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_ROOTis the only state directory. Resolved once byscripts/run-nanoagent.mjsand exported to the child. There is no~/.nanoagent, no~/.qwen-agent-tui, no<cwd>/.envlookup, no<cwd>/skillslookup, no legacy read fallback. SetNANOAGENT_ROOT=/path/to/installand every other path follows.One boot script.
nanoagent/nanogent/nano-agent/npx @omega3_0/nanoagentall dispatchscripts/run-nanoagent.mjs. The launcher creates the layout (config/,skills/,tools/,sessions/,workspace/,logs/) on first run, setsNANOAGENT_ROOT, and chdirs the child soprocess.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/logsSkills 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.jsonand<name>/SKILL.mdskills 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.jsonand project overrides only when--workspaceis passed.src/config/paths.tsis the new ground truth.installRoot(),nanoagentPaths(),GLOBAL_CONFIG_FILE(),SKILLS_DIR(),SESSIONS_DIR(), etc. all readNANOAGENT_ROOTand throw on missing subdirs.configDir/legacyConfigDir/configFileCandidatesare 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 inmcpUntrustedand 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.tsrefuses to run withoutNANOAGENT_ROOT. Trying tobun src/main.tsdirectly 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(therun_commandlifecycle test and the launcher-help test needbunon 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_PATTERNSlist (src/security/patterns.ts) was rejecting commands that small models had to run, with no path through theask/always_allowpermission flow.SecurityManager.validateCommandis now a structural validator (empty check + your customblockedCommands/allowedCommandslists); thePermissionManagerpolicy gate (ask/allow_edits/always_allow/read_only) is the sole safety net for shell commands. Review each command when prompted, then choosealways_allowfor the ones you trust.SECURITY.mdsynced. - Disabled skills are visible in
/autocomplete. Project-local skills (./skills/) correctly default toenabled: 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 theKEY_nmembers stripped by the sensitive-var filter, and git dies on the partial family ("missing config key").getSanitizedEnvnow treats the family as all-or-nothing. - Config precedence —
~/.nanogent.jsonsilently overwrote explicitly-passed options, so an explicit--base-urlcould be replaced by the saved/connectselection and the catalog would resolve the wrong provider's API key. Explicit options now win. - Test hermeticity — scrub lists cover every trusted
.envlocation via the canonicalconfig/.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_filethat cut the content field itself, and a broken parse madewrite_fileempty 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_linesreject truncated or unparseable arguments with an actionable error instead of writing partial/empty content. - Line-number echo stripping — small models copied
read_file'sNNNN|display prefixes into write/edit payloads; the tools now strip them (only on a strict ≥80%-of-lines, strictly-increasing match) and reportline_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.logfor 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.jsonliveness marker. Result: marker dirty, noexitevent — 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" whileexecute_commandactually 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).
