
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
@omega3_0/nanoagent
Advanced tools
Ultra-lightweight, scalable CLI/TUI coding agent optimized for tiny local models & remote runtimes
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.
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:
Please file issues at github.com/leeno7786-coder/nanoagent/issues. Do not treat this as production-ready without testing it on your own workload.
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.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.nanoagent, nanogent, nano-agent, or npx @omega3_0/nanoagent/queue to list, remove, or clearquestion 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.read_only / ask / allow_edits / always_allow, plus Shift+Tab to cycle in the TUIexplore_subagent workers against a pool you configure in /settings → Sub-agents (endpoint + model + parallel lanes); no model is assumed for you/mcp, /mcp-add, /mcp-remove); only the canonical global config is trusted by defaultNANOAGENT_ROOT/skills/, auto-load on triggers, /skills overlay (F8)/graph build|stats|report for codebase structurerun, doctor, models, todo for scripts and CInpm 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.
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
npx (no install)npx @omega3_0/nanoagent
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.
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
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.
| 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>.
Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF (or another Qwen 3.5 2B–8B)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/v1Sub-agents are opt-in and fully user-configured — nothing is auto-detected. Open /settings → Sub-agents and set:
http://127.0.0.1:1234/v1 for LM Studio, https://openrouter.ai/api/v1 for cloud, …)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.
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.
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.
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: false in the skill's frontmatter or config/skill-config.json keeps it dormant without deleting itTriggers in the frontmatter or Use when: ... clauses in the description auto-load skills on matching user prompts.
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:
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.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.! 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.
| 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.
| 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) |
| 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 |
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
Enabled by default:
PermissionManager policy gate is the safety net/allow a path.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.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.
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
[REDACTED] fragments.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.streamChat, so explore_subagent against local Qwen 2B models gets the same fix.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.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..deb keeps its linux-x64 Bun runtime, so the TUI starts from the Debian package (previously it could only run headless).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.question instead of listing options in prose.question call so the overlay still opens. Headless nanogent run skips promotion (no overlay).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.+x −y instead of a mashed hunk blob.Git Status / Git Diff hide a target of .; list_dir and git_status show a short name preview.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.notice-recovery-* and stay off the main chat panel..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.v* tag version = package.json version before publish.bun install --frozen-lockfile and npm run ci before packing/publishing..deb, and Windows zip to the GitHub Release.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.<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./changes./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./cd now warn if a baseline still cannot be taken (was debug-only).listSnapshots newest-first order is pinned so same-millisecond captures cannot flake. Suite: 995 pass.createCliRenderer so keyboard input is not lost on startup.shutdownAgent passes messageQueue through to autoSaveSession so headless/CLI exit paths do not drop queued messages.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.providerId + modelId matches to reduce cross-provider collisions./queue remove.explore_subagent error path updated to config/nanogent.json; duplicate maxBackgroundSubAgents removed from advanced settings./queue — list, /queue remove <N>, and /queue clear.question tool — first-class clarifying picker for ambiguous user requests (stack, features, approach). Single/multi-select with optional custom text.question, not because of a tool-round check-in or an API error.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.prompt_tokens are used as the live context baseline when available; local message growth and tool-schema/chat-template overhead are tracked on top.ContextManager accounting.explore_subagent are advertised only when a pool is configured.v* tag through GitHub Actions.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.maxTokens or switching models.↻ Model produced thinking only… (n/max) notice makes the loop transparent./config set maxReasoningOnlyRounds still overrides.<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.--workspace / QWEN_WORKSPACE → launch directory → install-root workspace (fallback for tests/embedded use). /cd still switches at runtime.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.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).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./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./ 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.A ground-up visual cleanup of the OpenTUI interface, aimed at a quieter, more polished chat surface.
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.→/←/$ glyphs, accent-highlighted targets, workspace-relative paths, multi-line commands truncated to one line, no per-message token noise.`code`, **bold**, ## headings)./ suggestions render directly above the input with accent-bar selection and right-aligned F-key hints, not as a modal.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./rollback will revert to.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).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.<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.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).
Two fixes that unblock agent work:
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./ 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.The 7 "flaky" local failures turned out to be two genuine bugs plus a test gap:
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.~/.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..env location via the canonical config/.env.Full suite is green locally for the first time: 909 pass / 0 fail.
Fixes the "models mangle files" complaint, three ways:
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.write_file / edit_file / edit_file_lines reject truncated or unparseable arguments with an actionable error instead of writing partial/empty content.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.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:
crash.log for uncaught exceptions/rejections. Result: nothing logged — JS handlers never fired.~/.nanoagent, giving diagnostics a stable home.last-run.json liveness marker. Result: marker dirty, no exit event — looked like a native-level kill.Received SIGINT, shutting down gracefully... — users were Ctrl+C'ing out of a garbled frame, not experiencing a native panic at all.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).
FAQs
Ultra-lightweight, scalable CLI/TUI coding agent optimized for tiny local models & remote runtimes
We found that @omega3_0/nanoagent demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.