A free, open-source alternative to AlphaSchool that runs on your own computer and remembers your child across every session.
Parents who want more for their kids — without a $200/month subscription.
If you've heard of AI tutoring platforms like AlphaSchool and thought "my child could really benefit from that," nanogo is built for you. It gives your child a patient, knowledgeable tutor available any time of day or night — one that never gets tired, never judges, and picks up exactly where it left off last time.
You don't need to be a programmer to use it. If you can open a terminal and paste a command, you're ready.
Most AI tutoring tools are either:
- Expensive — subscription fees that add up fast
- Generic — one-size-fits-all lessons with no memory of your child's progress
- Cloud-dependent — your child's learning history lives on someone else's server
nanogo is different:
- Free to run — you only pay for the AI calls you make (a few cents per session using cheap models)
- Remembers your child — it builds a persistent memory of what your child knows, struggles with, and enjoys
- Runs on your computer — your family's data stays with you
- Fully customizable — create lessons, quizzes, and study plans by writing simple text files
- Voice mode — Phase 19 adds contract-backed speech-to-text and text-to-speech so a child can speak to the tutor and hear responses when a voice provider is configured
- Extensible operator interfaces — Phase 19.7 adds a shared gateway surface so the same tutor runtime can be driven from a local TUI, an OpenAI-compatible HTTP API, or an OpenClaw-style WebSocket gateway
- Local interactive help — Phase 19.9 adds deterministic help topics that can be searched from the CLI, TUI, GatewayWS, and the OpenAI-compatible operations endpoint without calling an LLM
- Hardened extension boundaries — Phase 21.x keeps the kernel small, makes runtime extension registration auditable by capability family, and moves file-backed session persistence into
modules/session - Optional local browser lessons — The browser-control phase adds config-gated local browser sessions for interactive lessons, with deterministic fake tests and an
agent-browseradapter for manual headed smoke runs
For parents, Phase 19 voice support matters because it makes tutoring less like typing into a chatbot and more like working with a patient adult helper. Younger learners can answer out loud before they type confidently, parents can turn listening or speaking on and off during a session, and raw microphone audio is not persisted by default.
Phase 19.7 matters for families and builders who want more than one way to operate the tutor. A local terminal operator can monitor sessions in a TUI, existing OpenAI-compatible clients can call nanogo through /v1/chat/completions, and future apps can subscribe to normalized gateway events without coupling to the core agent loop.
Browser-control support matters when a lesson is more than a chat transcript. Nanogo can open a local lesson page, inspect accessible controls, click or fill answers, capture screenshots as local artifacts, and close the session when a trusted lesson wrapper reports completion. It is off by default so normal tutoring and tests do not require a browser.
The hardened browser path adds an extra safety layer for families using local interactive lessons. Lesson completion now requires a per-session nonce from the trusted wrapper, local file lessons are checked against symlink-safe roots, external scripts in local wrapper HTML are rejected by default, and browser session records avoid sensitive data such as profiles, cookies, storage, auth headers, and screenshots.
Ask it to tutor your 10-year-old in fractions:
nanogo -p "Explain fractions to a 10-year-old. Start with what half a pizza means, then ask me a question."
Come back tomorrow and it already knows where you left off:
nanogo -p "I'm back. What should we work on today?"
It answers: "Welcome back! Last time we covered equivalent fractions and you were doing great. Want to try a short quiz on adding fractions with different denominators?"
Run a structured lesson defined in a file — for example, a daily math warm-up:
nanogo skill run daily-math --grade=4 --student=Emma
nanogo is written in Go. Download and install it from https://go.dev/dl (free). Choose the installer for your operating system (Mac, Windows, or Linux) and follow the on-screen instructions. You only need to do this once.
Verify it worked by opening a terminal and typing:
go version
You should see something like go version go1.22.0.
git clone https://github.com/tvmaly/nanogo.git
cd nanogo
go build -o nanogo ./cmd/nanogo
This creates a nanogo program in the current folder.
nanogo uses AI models through OpenRouter — a free-to-join service that gives you access to powerful AI at very low cost. Sign up at https://openrouter.ai, then create an API key in your account dashboard.
Set it in your terminal (replace your-key-here with your actual key):
Mac / Linux:
export OPENROUTER_API_KEY=your-key-here
Windows (Command Prompt):
set OPENROUTER_API_KEY=your-key-here
Phase 15 includes an optional realtime voice extension. The first provider target is xAI Grok Voice. You need an xAI account with API access and purchased credits before live voice calls will work.
Mac / Linux:
export XAI_API_KEY=your-xai-key-here
export XAI_REALTIME_MODEL=grok-voice-think-fast-1.0
Windows (Command Prompt):
set XAI_API_KEY=your-xai-key-here
set XAI_REALTIME_MODEL=grok-voice-think-fast-1.0
Deepgram Voice Agent is planned behind the same adapter pattern for later testing:
Mac / Linux:
export DEEPGRAM_API_KEY=your-deepgram-key-here
Windows (Command Prompt):
set DEEPGRAM_API_KEY=your-deepgram-key-here
To avoid setting this every time, add it to your shell profile (.zshrc or .bashrc on Mac/Linux, or System Environment Variables on Windows).
Optional browser lesson setup:
scripts/setup_agent_browser.sh
./nanogo browser doctor --driver agent-browser
./nanogo browser open --driver agent-browser --headed https://example.com
./nanogo browser smoke-duckduckgo --driver agent-browser --headed
./nanogo browser smoke-youtube --driver agent-browser --headed --seconds 75
./nanogo --workspace ./workspace browser smoke-youtube-iframe --driver agent-browser --headed
Browser support is disabled by default. To expose browser tools to agents or gateway operations, add:
{
"browser": {
"enabled": true,
"driver": "agent-browser",
"allow_file_roots": ["workspace/lessons"],
"artifact_root": ".nanogo/browser-artifacts",
"max_sessions": 2
}
}Trusted local wrapper lessons under allow_file_roots can report progress, quiz_answer, error, and completion events, but completion closes the session only when the wrapper includes the session nonce returned by browser_session_start. browser_eval remains policy-gated and reports that JavaScript-initiated network requests are not isolated by Nanogo domain policy.
Manual headed live-web smoke commands write redacted JSON artifacts under .nanogo/browser-smokes by default. Each artifact includes the final URL, title, screenshot path, console/network summaries, and cleanup status. The YouTube iframe smoke defaults to the checked-in local fixture at workspace/lessons/yoyo_youtube_iframe/index.html; use --url to point it at another trusted wrapper.
./nanogo -p "Hello! I am a parent setting this up for my 8-year-old. Can you introduce yourself and ask what subject they want to study today?"
Lessons in nanogo are plain text files called "skills." Create a folder called skills/ and add a file called math-basics.md:
---
name: math-basics
description: Daily math warm-up for kids
args:
- grade
- student
---
You are a friendly math tutor. Your student is {{student}}, who is in grade {{grade}}.
Start with a warm greeting, then give them three math problems appropriate for their grade level.
After they answer each one, give encouraging feedback and move to the next.
At the end, summarize how they did and suggest what to practice next.Run it with:
./nanogo --skills=./skills skill run math-basics --grade=3 --student=Emma
nanogo will ask for any missing information interactively if you forget to supply it.
You can make a skill for any subject: reading comprehension, spelling practice, science questions, history flashcards, foreign language vocabulary — anything a human tutor could teach.
After each session, nanogo automatically:
- Saves the conversation to a local history file on your computer
- Runs a background pass that reads the history and updates a
MEMORY.mdfile - Loads that memory at the start of every future session
This means the tutor genuinely remembers across days and weeks:
- Which topics your child has covered
- Where they struggled and where they excelled
- Their name, grade level, and learning style
- What you worked on in the last session
Everything is stored as plain text files in ~/.nanogo/workspace/ on your own machine — you can read and edit them any time.
nanogo can write schema-versioned observation records for agents and research loops as JSONL. Add the jsonl observer to your config:
{
"obs": [
{
"driver": "jsonl",
"config": {
"root": ".nanogo/obs",
"failure_policy": "best_effort",
"flush_on_error": true,
"flush_on_run_finish": true
}
}
]
}By default this writes one JSON object per line to .nanogo/obs/observations.jsonl. Each record includes schema_version, id, type, time, event lineage in attributes.event_kind when it comes from the runtime event bus, optional artifact references, error details, repair hints, and links. Query contracts are present for future readers; the JSONL store currently returns a typed not-implemented error for queries.
Run the live OpenRouter smoke suite with:
OPENROUTER_API_KEY=sk-or-v1-... make testThe meta research loop can create deterministic draft lesson artifacts without calling a model or starting a preview server:
nanogo --workspace ./workspace meta lesson create \
--kind manim_lesson \
--prompt "Teach multiplying fractions to a 9-year-old" \
--runner fake
nanogo --workspace ./workspace meta lesson create \
--kind browser_game_lesson \
--prompt "Teach states of matter with a drag-and-drop sorting game" \
--runner fakeThese commands write run-scoped candidates under lessons/generated/<lesson-id>/runs/<run-id>/, including lesson.bundle.yaml, source files, logs, validation reports, static preview files, and fake video or browser-game artifacts. Lineage and graph evidence are appended under memory/meta/. Passing smoke gates make a candidate eligible for promotion, but the command does not promote it.
For parents, this means nanogo can produce inspectable draft animations and interactive activities before anything becomes a stable lesson. For developers, the first slice is fake-backed and deterministic; real Manim, Vite, and Playwright execution remains a manual follow-up.
Phase 19 separates voice into stable contracts and optional providers:
core/contractsdefines speech-to-text and text-to-speech interfaces, audio formats, transcript events, and TTS stream events.modules/voiceowns the reusable voice session controller, final-transcript routing, automatic speaking,voice_say, and runtime STT/TTS toggles.ext/voice/providers/applecontains Apple SpeechAnalyzer and AVSpeech provider entry points, kept outside the kernel.- CLI commands include
nanogo voice providers,nanogo voice stt,nanogo voice tts, andnanogo voice chat.
Runtime voice state is session-scoped. A future web interface can use the same control path as the CLI to turn listening or speaking on and off without starting a new tutoring session.
Manual Apple smoke targets:
make apple-voice-helpers
make test-19.apple-tts
make test-19.apple-sttThese require macOS voice capabilities and local microphone/speaker permissions. Deterministic Go tests use fakes and do not require real voice providers.
Phase 19.5 replaces the Apple placeholders with optional Swift helper binaries. The Go providers talk to those helpers over JSONL stdio, so ordinary Go builds and tests remain portable and do not require Swift, macOS, a microphone, a speaker, or Apple speech assets.
To build the helpers on a supported Mac:
make apple-voice-helpersThe helper build skips on non-Mac systems. On macOS it requires swiftc and a macOS SDK 26+ because the STT helper uses Apple's SpeechAnalyzer, SpeechTranscriber, and AssetInventory APIs. Helper paths can be overridden with NANOGO_APPLE_AVSPEECH_HELPER and NANOGO_APPLE_SPEECHANALYZER_HELPER.
Live Apple STT/TTS chat:
make test-19.5.apple-voice-chatDirect CLI form:
./nanogo voice chat --stt apple --tts apple --locale en-USThis path uses local microphone capture, Apple speech-to-text, the configured OpenRouter-backed nanogo agent, Apple text-to-speech, and local speaker playback. Raw microphone and speaker PCM are not persisted by default.
Phase 19.6 routes voice chat through the full nanogo agent loop, so voice turns can use the same configured tools as text turns. ask_user is intentionally hidden in voice chat until nanogo has a voice-native ask/answer flow.
OpenRouter web search is available in voice chat by default as a provider-side server tool. The voice system prompt tells the model to use search only when you explicitly ask it to search, browse, look up information, or answer current/latest factual questions.
Advanced web search settings can be configured:
{
"voice": {
"web_search": {
"engine": "auto",
"max_results": 8,
"max_total_results": 20,
"search_context_size": "medium",
"allowed_domains": ["arxiv.org", "nature.com"],
"excluded_domains": ["reddit.com"]
}
}
}Useful live prompts:
What tools do you have available in this voice session?
Use a tool to list the files in this repository root, then summarize what you found.
Search the web for the latest OpenAI model news and summarize the top results.
Explain magnets to a 10-year-old without searching the web.
Phase 17 moved nanogo toward a smaller execution kernel and more behavior in modules and editable workspace assets. The preferred place to change tutor behavior is now plain files before Go code:
workspace/tools/<name>/tool.yaml
workspace/tools/<name>/prompt.md
workspace/tools/<name>/tests.yaml
workspace/skills/*.md
workspace/tutorials/*.md
workspace/policies/*.md
Workspace tool definitions are validated by ext/workspace: missing commands, unknown manifest fields, path traversal, duplicate names, and missing tests fail with field-specific errors. Phase 17.5 adds schema_version metadata to workspace tool manifests so future workspace assets can evolve without ambiguous migrations.
For local verification, run:
make verify-localThat target builds the binary, runs normal and race-detector tests, runs go vet, checks the Phase 17 core boundary, enforces the core/ and modules/ import invariants, checks required fakes, and verifies the core LOC budget.
Current architecture split:
| Directory | Role | Examples |
|---|---|---|
core/ |
Tiny execution kernel and stable contracts used directly by the agent loop. | agent, contracts, event, harness, llm, session, tools |
modules/ |
Stable first-party runtime subsystems: shared contracts, registries, and default product behavior. | modules/obs, modules/transport, modules/memory, modules/tools/builtin |
ext/ |
Optional concrete adapters, providers, domains, and experiments that plug into core or module contracts. | ext/obs/slog, ext/transport/rest, ext/scheduler/cron, ext/adaptive, ext/voice |
workspace/ |
Editable data-first behavior and user/product assets. | workspace/tools/..., workspace/skills/*.md, workspace/policies/*.md |
A useful rule of thumb: modules/ is the first-party subsystem layer, while ext/ is the plug-in and adaptation layer. For example, modules/obs defines the shared observability API and fan-out behavior, while ext/obs/slog, ext/obs/file, and ext/obs/cost are concrete observers. Similarly, modules/transport defines the transport contract and registry, while ext/transport/cli, ext/transport/rest, and ext/transport/webui are concrete transports.
Not every modules/ package is interfaces-only. modules/memory, modules/skills, and modules/tools/builtin contain default first-party behavior. They stay outside core/ because they are product/runtime behavior rather than kernel primitives.
Phase 21.x hardens the extension boundary without changing user-facing tutor behavior:
core/sessionnow contains only stable session contracts; the JSONL and metadata-file store lives inmodules/sessionand preserves existing<id>.jsonland<id>.meta.jsoncompatibility.cmd/nanogo/extensions.gogroups package-init registrations by LLM providers, transports, schedulers, harness sensors, and adaptive domains/tools, with command-package tests that fail when expected runtime registrations disappear.modules/gatewayregisters operations through feature-family functions for status, chat/sessions/events, skills/tools, costs/models, voice/realtime, and help.- Architecture guards now have explicit regression coverage for
core -> modules,core -> ext, andmodules -> extimport violations.
Phase 17.5 closes runtime-wiring gaps that matter for Phase 18 AgentFlow tutor flows:
spawnis now wired through configured subagent runners in prompt, REPL, skill, heartbeat, and transport-driven turns, so future flow executors can delegate without bypassing nanogo sessions, tools, events, or concurrency limits.- Configured transports use a shared
modules/transport.Appadapter over the existing agent loop, which reduces one-off CLI/REST/WebUI composition paths before addingflow run. - Voice sessions persist normalized events by default, while raw provider event logs require explicit opt-in, reducing privacy risk for child transcripts and provider payloads.
- Adaptive archive records, voice event records, raw voice logs, and workspace tool manifests now include schema-version metadata to make evidence archives safer to inspect and migrate.
Phase 18 v3 interface readiness adds a narrow core/contracts package for cross-cutting capability boundaries without moving product behavior into the kernel:
core/contractsdefines small agent, subagent, tool runtime, pattern runtime, handoff, trace, and approval interfaces plus request/result shapes for Phase 18 v3 orchestration.core/tools.NewContractRuntimeadapts existingcore/tools.Sourcevalues into the new tool catalog/invocation contracts while preserving the existing tool API.core/agent.SubagentRunnernow also satisfies the neutral subagent spawner contract, and builtinspawnkeeps its existing compatibility behavior.- Voice provider adapters, including xAI realtime voice, remain in
ext/voice; future voice orchestration can depend oncontracts.PatternRunnerorcontracts.HandoffTargetat the bridge boundary.
Phase 18 v3 adds a concrete contract-backed pattern runtime outside core:
ext/agentpatterns.Runtimeimplementscontracts.PatternRuntimeand keeps router, supervisor-worker, sequential, parallel, loop, review, handoff, and human-review behavior out ofcore/contracts.- Pattern execution uses injected
contracts.ToolRuntime,contracts.SubagentSpawner,contracts.TraceSink, andcontracts.ApprovalGate; tests use deterministic fakes by default. - Skills can opt into patterns with frontmatter such as
mode: agentpatterns,pattern: sequential, andpattern_manifest: fraction_lessonwithout importing the concrete extension package. - Setting
"agent_patterns": {"enabled": true}in config addspattern_run,pattern_status,pattern_resume, andpattern_listto the tool source. - Pattern traces and checkpoints are extension-local JSONL surfaces; trace redaction is enabled in the concrete JSONL sink.
| Feature | What it means for you |
|---|---|
| Persistent memory across sessions | The tutor remembers your child's progress every time — no need to re-explain the basics each session |
| Custom lesson files | Write a simple text file to define any lesson, quiz, or study plan — no coding required |
| Interactive Q&A | If the tutor needs more information (grade, topic, name), it asks your child directly |
| Your data stays home | All history and memory files live on your own computer — nothing is sent to a third-party server beyond the AI call itself |
| Any subject | Math, reading, science, history, coding, languages — if a human tutor could teach it, nanogo can too |
| Always available | 3am panic before a test? nanogo is there, patient as ever |
| Very low cost | A typical 20-minute tutoring session costs less than one cent using the default model |
| Multiple tutor personalities | Define a strict grammar checker, an encouraging math coach, and a Socratic science guide — each as a separate skill file |
| Open source and free forever | No subscription, no lock-in. Audit the code, modify it, share it with other parents |
Phases 12–14 introduce a self-improving education loop that no static tutoring app can match. Here is what that means in plain language:
Turn a rough idea into a ready-to-use lesson. Write a few sentences about what you want your child to learn — "I want Emma to understand fractions using cooking recipes" — and the system builds a complete, leveled lesson bundle: introduction, worked examples, practice questions, a rubric, and a parent summary. You review and approve before anything reaches your child.
Lessons improve over time based on your child's actual results. Every session generates a signal — did your child master the concept, get frustrated, breeze through, or stall on a specific step? The system archives those outcomes and, over multiple sessions, shifts toward lesson variants and teaching approaches that work best for that specific child. You can see exactly which variants are running and why.
No two children get the same tutor. A child who learns visually by analogy gets different explanations than one who prefers step-by-step worked examples. The system tracks what works per child and applies it automatically.
You stay in control. Every generated lesson requires parent approval before it is used. You can inspect, edit, or reject any variant. The archive is plain text files on your machine — readable and auditable at any time.
Rapidly prototype differentiated instruction. Write a single rough prompt per topic and let the factory generate multiple pathways: a visual/analogical path, a step-by-step procedural path, a challenge path for advanced students, and a remediation path for students who struggle. Each pathway is a plain markdown file you can edit directly.
Data-driven lesson refinement without a data science background. Mastery scores, retention signals, and engagement data accumulate automatically. The system surfaces which pathways produce the best outcomes per student profile so you can spend your time teaching, not analyzing spreadsheets.
Reusable skill libraries. Every lesson produced by the factory becomes a reusable skill file. Build a library of proven, child-tested lessons over a semester and share them with other families or colleagues — they are plain text and require no special tools to read or run.
nanogo uses anthropic/claude-haiku-4-5 by default via OpenRouter:
| Session | Approximate cost |
|---|---|
| 10-minute tutoring session | ~$0.005 (half a cent) |
| 30-minute deep dive | ~$0.015 |
| A full week of daily sessions | ~$0.10 |
For comparison: AlphaSchool costs roughly $2500-$5000/month. nanogo's AI costs for equivalent usage run under $2/month.
Open an issue at https://github.com/tvmaly/nanogo/issues. Parent feedback directly shapes the roadmap.
This table shows each build phase, what AI tutor capability it unlocks, and whether it is complete.
| Phase | Description | AI Tutor Capability | Status |
|---|---|---|---|
| 1 | Event bus + LLM interface + Router + OpenAI ext + CLI transport | Basic single-question tutoring — child asks, tutor answers | ✅ Complete |
| 2 | Tool interface + 5 builtins + agent loop + session + subagent concurrency | Tutor can read/write files, run code, and delegate to specialist sub-tutors (math agent, grammar agent) | ✅ Complete |
| 3 | Skills frontmatter + dispatcher + ask_user integration |
Named lesson plans ("do my math homework", "quiz me on fractions") — tutor asks for missing details interactively | ✅ Complete |
| 4 | Memory (consolidator + dream + curator) | Tutor remembers your child across sessions — past mistakes, strengths, learning style, goals | ✅ Complete |
| 5 | REST + REPL transports | Multi-interface access — tutor available via browser/API (REST) and interactive terminal (REPL) simultaneously | ✅ Complete |
| 6 | Harness interfaces + sensors + binding-signal support | Tutor self-corrects when it makes a mistake — test failures inject feedback that forces revision | ✅ Complete |
| 7 | Scheduler + heartbeats (4 action kinds) + CLI management | Scheduled tutoring — daily vocabulary quiz at 8am, weekly progress review on Fridays | ✅ Complete |
| — | Post-phase-7 integration fixes: CLI transport init() registration, router factory in ext/llm/router/, signal injection wired, SubagentRunner isolated sessions, session-backed ask_user, tools allowlist, config loading from ~/.nanogo/config.json |
All runtime-wiring gaps from REVIEW.md closed; Phase 8 prerequisites met | ✅ Complete |
| 8 | Obs interfaces + slog + file + cost adapter | Full observability and per-session cost tracking — know exactly what you spent and on what | ✅ Complete |
| 9 | Evolve extension (full, test-gated) | Self-improving tutor — agent proposes improvements to its own lesson files, tests them, deploys on green | ✅ Complete |
| 10 | Telegram + cron + otel + progressive tools + MCP + mutants + classifier-router | Full ecosystem — tutor on Telegram, mutation-tested lesson scripts, multi-model routing by difficulty | ✅ Complete |
| 10.5 | Contract-driven progressive tool design | Nanogo can expose small, safe tool surfaces by default and author new tools from clear operation contracts, manifests, compact outputs, and tests | ✅ Complete |
| 11 | Web tutor UI extension: student lessons + parent admin + reporting | Family-friendly browser experience — student lessons, parent dashboards, lesson editing, and homeschool reporting | ✅ Complete |
| 12 | Adaptive experiment engine: artifacts, outcomes, archive, islands, scoring | System learns which lesson variants actually work — tracks mastery gain, engagement, and retention per child | ✅ Complete |
| 13 | Adaptive lesson factory: rough parent markdown → polished child-specific lesson bundles | Parents write a rough idea; the system generates complete, leveled lessons tailored to their child's style | ✅ Complete |
| 14 | Adaptive tutor runtime: live policy selection, mastery scoring, remediation, evolve loop | Tutor adapts its teaching style in real time — hints, pacing, difficulty, and encouragement evolve per child | ✅ Complete |
| 15 | Realtime voice extension: xAI Grok Voice backend sessions, voice smoke CLI, optional local audio evaluation | Talk to the tutor through a backend voice session now, with browser microphone/speaker handling still cleanly separable for later phases | ✅ Complete |
| 15.5 | Live hands-free voice loop: MacBook microphone capture, xAI realtime streaming, speaker playback | Talk to the tutor hands-free from the command line with raw audio kept private unless debug files are requested | ✅ Complete |
| 17.5 | Phase 18 readiness hardening: spawn runtime wiring, configured transport app adapter, schema-versioned evidence, module import guard, raw voice log opt-in | Complex tutor flows can safely delegate, run through configured transports, and keep auditable evidence without exposing raw provider data by default | ✅ Complete |
| 18 v3 interface prerequisite | Narrow core/contracts package plus tool and subagent adapters |
AgentFlow-style runtimes can depend on stable kernel contracts for tools, subagents, handoff, traces, and approvals without importing concrete product extensions | ✅ Complete |
| 18 v3 pattern runtime | Contract-backed ext/agentpatterns runtime, pattern tools, skill opt-in, voice bridge handoff, trace/checkpoint stores |
Tutor workflows can route through single, router, supervisor-worker, sequential, parallel, loop, review, handoff, and human-review patterns without leaking extension code into core or modules | ✅ Complete |
| 19.7 | Extensible interface surface: shared modules/gateway, Bubble Tea TUI, OpenAI-compatible HTTP API, and OpenClaw-style Gateway WebSocket |
Operators and apps can drive the same tutor sessions, skills, tools, costs, and events through stable adapter contracts without adding interface logic to core/ |
✅ Complete |
| 19.8 | Rich Bubble Tea TUI with slash commands, session-local model switching, model catalog cache, cost lookup, STT/TTS toggles, and xAI realtime controls | The terminal operator can chat, inspect sessions, run skills, monitor costs/events, switch OpenRouter models per session, and control voice state without widening core/ |
✅ Complete |
| 21.x | Extension boundary hardening: module-owned session persistence, grouped runtime registrations, gateway operation families, and stronger import-guard evidence | New providers, transports, schedulers, tool sources, adaptive domains, and gateway features can grow without widening core/ or hiding command composition changes |
✅ Complete |
Use this section if you want the shortest path from clone to a working local build and the current acceptance test suite.
You need:
- Go 1.22 or newer
- Git
- An OpenRouter API key for live LLM tests or manual runs
Verify the basics:
go version
git --versiongit clone https://github.com/tvmaly/nanogo.git
cd nanogogo build -o nanogo ./cmd/nanogoMac / Linux:
export OPENROUTER_API_KEY=your-key-hereWindows Command Prompt:
set OPENROUTER_API_KEY=your-key-here./nanogo -p "Reply with exactly: OK"If your setup is working, the response should begin with OK.
go test -race ./...
scripts/check_imports.sh
scripts/loc_budget.sh
scripts/check_fakes.shIf you also want the coverage gate used from Phase 4 onward:
go test -coverprofile=cover.out ./core/agent/... ./modules/memory/... ./modules/tools/builtin/...
go tool cover -func=cover.out | tail -1Phase 10.5 adds a contract-first tool layer for extension authors and future self-evolution work. New tools can be written as operations with one clear contract: name, description, input schema, output schema, bounded output rules, safety metadata, data-access mode, examples, and tests. Nanogo adapts that contract into core/tools.Tool values, generated help, manifests, and optional future CLI/MCP surfaces without duplicating invocation logic.
Progressive disclosure keeps the default tool surface small. A manifest can make only tool_list, tool_help, tool_reveal, and a few safe tools visible at first, while hidden tools remain real callable tools once revealed for the current session. The agent loop refreshes tool schemas after reveal, so a tool can be revealed and used in the same turn.
Configured tool sources use the tools.sources config block. With no config, nanogo keeps the existing builtin-only behavior.
{
"tools": {
"sources": [
{"driver": "builtin"},
{
"driver": "progressive",
"config": {
"manifest": "./tools/progressive.json",
"sources": [{"driver": "adaptive", "config": {"root": "~/.nanogo/workspace"}}]
}
}
]
}
}The manual tests exercise the full stack end-to-end against a real LLM via OpenRouter. They cover one test per completed phase in the order phases were delivered.
# Build the binary and run all manual tests in phase order:
make test
# Or run a single test by phase:
make test-1.9 # TEST-1.9 — real LLM round trip
make test-2.12 # TEST-2.12 — agent creates and reads a file
make test-4.9 # TEST-4.9 — memory persists across two sessions
make test-8.5 # TEST-8.5 — event kinds visible in log
make test-8.10 # TEST-8.10 — cost tracker records real turns
make test-9.8 # TEST-9.8 — evolve building blocks (sandbox, path guard, learnings)
make test-9.9 # TEST-9.9 — self-edit attack rejected by path guard
make test-12.20 # TEST-12.20 — adaptive experiment demo and inspect report
make test-13.24 # TEST-13.24 — lesson factory compile/review/approve/assignmake test will fail immediately if OPENROUTER_API_KEY is not set.
-
Build the binary:
go build -o /tmp/nanogo ./cmd/nanogo
-
Set your API key:
export OPENROUTER_API_KEY=sk-or-v1-... -
Write a config file (
/tmp/nanogo-test-config.json):{ "llm": { "driver": "openai", "config": { "base_url": "https://openrouter.ai/api/v1", "api_key_env": "OPENROUTER_API_KEY", "model": "anthropic/claude-haiku-4-5" } }, "transports": [{"driver": "cli"}] } -
Run each test in order:
TEST-1.9 — Real LLM round trip
/tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "Reply with exactly: OK" # Pass: output contains OK
TEST-2.12 — Agent performs file edit
/tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "Create a file /tmp/nanogo-demo.txt containing exactly the word 'hello', then read it back and tell me its contents." cat /tmp/nanogo-demo.txt # Pass: file exists and contains "hello"
TEST-4.9 — Memory persists across sessions
/tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "Remember that my favorite programming language is Go." /tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "What is my favorite programming language?" # Pass: second response mentions Go
TEST-8.5 — All event kinds visible
/tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "Create a file /tmp/nanogo-event-test.txt with content 'y'" jq -r '.kind' /tmp/nanogo-workspace/log.jsonl | sort -u # Pass: includes turn.started, turn.token, tool.started, tool.result, turn.completed
TEST-8.10 — Cost tracker records real turns
/tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ --skills testdata/skills \ -p "Reply with OK" /tmp/nanogo --config /tmp/nanogo-test-config.json \ --workspace /tmp/nanogo-workspace \ cost # Pass: cost.jsonl exists or cost summary prints
TEST-9.8 — Evolve building blocks
go test -v -run "TestSandbox|TestPathGuard|TestLearnings|TestSynthesis" ./ext/evolve/... # Pass: all four tests green
TEST-9.9 — Self-edit attack rejected
go test -v -run "TestPathGuard|TestPathGuardLearningsEntry" ./ext/evolve/... # Pass: IsBlocked returns true for core/ and ext/evolve/ paths; rejection logged
TEST-12.20 — Adaptive experiment smoke
/tmp/nanogo --workspace /tmp/nanogo-workspace adaptive demo --child cross --subject science --topic magnets /tmp/nanogo --workspace /tmp/nanogo-workspace adaptive inspect --child cross --subject science --topic magnets # Pass: demo selects a winner, writes child patterns, and inspect prints top artifactsTEST-13.24 — Lesson factory workflow
mkdir -p /tmp/nanogo-workspace/inbox/lessons cp ext/adaptive/domains/lessonfactory/testdata/magnets.md /tmp/nanogo-workspace/inbox/lessons/magnets.md /tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory compile --source /tmp/nanogo-workspace/inbox/lessons/magnets.md /tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory review --lesson latest /tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory approve --lesson latest /tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory assign --lesson latest --child cross # Pass: bundle, review, parent guide, child pathways, and assignment queue are written
Phase 12 adds an extension-only adaptive experiment engine under ext/adaptive/. It stores artifacts and outcomes under memory/adaptive/, scores outcomes across mastery, retention, transfer, engagement, quality, parent rating, frustration, time, and cost, and writes parent-readable reports.
Manual smoke:
./nanogo --workspace /tmp/nanogo-workspace adaptive demo --child cross --subject science --topic magnets
./nanogo --workspace /tmp/nanogo-workspace adaptive inspect --child cross --subject science --topic magnetsThe demo creates fake adaptive artifacts, records outcomes, selects a winner, writes memory/adaptive/child_patterns/<child-id>.md, and creates an inspect report under memory/adaptive/reports/experiments/.
Phase 13 adds ext/adaptive/domains/lessonfactory/, an extension-only adaptive domain that compiles rough parent-authored markdown into deterministic lesson bundles under lessons/generated/<lesson-id>/.
Generated bundles include lesson.yaml, parent_guide.md, child_summary.md, per-child default/hands-on/remediation pathways, age/depth levels, activities, quick checks, rubrics, transfer questions, retention review, sources.md, and review.md. Assignment is blocked until parent approval is recorded.
CLI workflow:
/tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory compile --source /tmp/nanogo-workspace/inbox/lessons/magnets.md
/tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory review --lesson latest
/tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory approve --lesson latest
/tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory assign --lesson latest --child crossThe domain registers adaptive artifact kinds for lesson bundles, pathways, rubrics, and templates. It also exposes a lessonfactory tool source with parse, compile, review, package, assign, parent-review, child-outcome, and template-mutation operations.
Phase 14 adds ext/adaptive/domains/tutorruntime/, an extension-only adaptive domain for live tutoring sessions. It selects tutor policies from parent pins and archive outcomes, records turn evidence, grades answers deterministically, updates mastery, schedules retention reviews, recommends remediation, tracks misconceptions through profile approval, mutates policy versions, and writes parent-readable session summaries.
Initial tutor policies live under ext/adaptive/domains/tutorruntime/policies/: socratic-guide, worked-example-first, hands-on-remediation, visual-analogy, story-explanation, retrieval-practice, challenge-mode, and gentle-coach.
Runtime files live under:
memory/adaptive/tutorruntime/sessions.jsonl
memory/adaptive/tutorruntime/turns.jsonl
memory/adaptive/tutorruntime/pending_reviews.jsonl
memory/adaptive/tutorruntime/strategy_switches.jsonl
memory/adaptive/tutorruntime/policy_activations.jsonl
memory/adaptive/artifacts/tutor_policies/
memory/adaptive/reports/tutorruntime/
Manual smoke:
make test-14.27The tutorruntime tool source exposes policy selection, turn recording, answer grading, mastery update, misconception detection, remediation recommendation, review scheduling, and session summary tools.
Phase 19.7 adds modules/gateway as the shared interface-facing service. The gateway owns operator operations for chat, sessions, skills, tools, costs, and normalized events while keeping core/ limited to stable agent, session, event, tool, and LLM contracts.
Phase 19.8 expands the local Bubble Tea TUI into the main terminal operator console. It adds slash commands for current-session costs, session-local OpenRouter model switching, STT/TTS toggles, and xAI realtime speech controls.
Phase 19.9 adds a contract-backed local help system. Help content lives in docs/help, domain behavior lives in modules/help, filesystem loading lives in ext/help/files, and transports access help through modules/gateway.
Build and run the local operator console:
make build
OPENROUTER_API_KEY=sk-or-v1-... \
/tmp/nanogo --config /tmp/nanogo-test-config.json \
--workspace /tmp/nanogo-workspace \
--skills testdata/skills \
tuiUseful TUI keys:
| Key | Action |
|---|---|
Tab / Shift+Tab |
Move between Chat, Sessions, Skills, Tools, Costs, and Events panes |
Enter |
Send chat input, select the highlighted session, or run the highlighted skill |
Up / Down |
Scroll Chat history, or move selection inside panes that support selection |
PageUp / PageDown |
Scroll Chat history by a page |
Home / End |
Jump to the oldest or newest visible Chat history |
n |
Create a session in the Sessions pane |
d |
Delete the highlighted non-current session in the Sessions pane |
r |
Refresh gateway data |
Esc / Ctrl+C |
Quit |
Supported TUI slash commands from the chat input:
| Command | Behavior |
|---|---|
/cost |
Show the current session's cost summary |
/model current |
Show the current session model |
/model list |
List available OpenRouter models, cached for 24 hours |
/model use <model-id> |
Switch only the active TUI session to the selected model |
/model flush |
Clear the model catalog cache so the next list refetches |
/stt on / /stt off |
Enable or disable speech-to-text state for the active TUI voice session |
/tts on / /tts off |
Enable or disable text-to-speech state for the active TUI voice session |
/xai on |
Start the xAI realtime speech control path when XAI_API_KEY is configured |
/xai status |
Show xAI provider, model, session, and connection status |
/xai off |
Stop the xAI realtime speech session and release resources |
/help |
Open contextual help suggestions without sending a chat message |
/help <query> |
Search local help topics |
/help topic <topic-id> |
Open one structured help topic |
/help validate |
Validate the configured help pack |
Use the built-in local help pack from the CLI:
/tmp/nanogo help
/tmp/nanogo help search gateway
/tmp/nanogo help tools.contracts
/tmp/nanogo help validateThe same help service is available through gateway operations:
{"method":"help.search","params":{"query":"voice privacy","limit":5}}
{"method":"help.topic","params":{"id":"voice.privacy"}}
{"method":"help.suggest","params":{"interface":"tui.chat","limit":5}}
{"method":"help.validate"}OpenAI-compatible clients should use POST /nanogo/v1/operations for help operations. Phase 19.9 intentionally does not add a dedicated help GET route and does not inject help as an implicit model tool into /v1/chat/completions.
Help topics are deterministic local content. Normal help lookup does not call an LLM, invoke tools, use embeddings, read vector stores, or write session history. The checked-in pack can be validated with:
go test ./modules/help/... ./ext/help/files/... ./modules/gateway ./ext/transport/tui ./ext/transport/gatewayws ./ext/transport/openaiapi ./cmd/nanogo
make test-19.9Non-interactive TUI smoke against OpenRouter:
OPENROUTER_API_KEY=sk-or-v1-... make test-19.8OpenAI-compatible HTTP API:
export NANOGO_GATEWAY_TOKEN=local-secret
/tmp/nanogo --config /tmp/nanogo-test-config.json \
--workspace /tmp/nanogo-workspace \
--skills testdata/skills \
openaiapi --addr :8081It exposes GET /healthz, GET /v1/models, POST /v1/chat/completions, and nanogo control endpoints under /nanogo/v1/.
Gateway WebSocket:
export NANOGO_GATEWAY_TOKEN=local-secret
/tmp/nanogo --config /tmp/nanogo-test-config.json \
--workspace /tmp/nanogo-workspace \
--skills testdata/skills \
gatewayws --addr :8082 --path /gatewayThe WebSocket adapter uses OpenClaw-style JSON text envelopes with a first connect request, v1 protocol negotiation, bearer auth, req/res frames, and normalized event frames. Client-supplied OpenAI tools are rejected by the HTTP compatibility layer; nanogo runtime tools remain server-side policy.
Phase 15 adds ext/voice/, an extension-only backend voice session layer that can later be consumed by the web UI. The internal contract is modeled around OpenAI Realtime-style events so provider adapters can be swapped without changing the session manager.
The first concrete provider target is xAI Grok Voice using:
wss://api.x.ai/v1/realtime?model=grok-voice-think-fast-1.0
Configuration uses XAI_API_KEY and XAI_REALTIME_MODEL. The xAI account must have voice/API credits enabled; buy credits from xAI before running the smoke or live voice commands. Deepgram Voice Agent is named as a planned second adapter using DEEPGRAM_API_KEY, with automated Deepgram tests deferred until requested.
The backend API supports voice session start, audio append/commit/clear, text-only turns, response creation, event streaming, transcript/raw event persistence under memory/voice/, and clean session close. The provider WebSocket layer uses github.com/coder/websocket.
Build and run a text-only xAI smoke test:
go build -o /tmp/nanogo ./cmd/nanogo
export XAI_API_KEY=your-xai-key-here
export XAI_REALTIME_MODEL=grok-voice-think-fast-1.0
/tmp/nanogo --workspace /tmp/nanogo-workspace voice smoke \
--provider xai \
--child cross \
--text "Say hello in one short sentence."Run the Makefile xAI smoke tests:
make test-15.17 # text-only realtime voice
make test-15.18 # raw PCM file input/outputEvaluate local microphone/speaker plumbing with github.com/gen2brain/malgo:
go build -tags malgo -o /tmp/nanogo ./cmd/nanogo
/tmp/nanogo --workspace /tmp/nanogo-workspace voice smoke \
--provider xai \
--child cross \
--mic \
--speaker
make test-15.19malgo is optional and isolated behind the malgo build tag because it uses cgo/miniaudio and may require local audio permissions. The backend voice session API does not depend on malgo, so browser-based audio can replace local device handling in a later phase.
Phase 15.5 adds a live local mic/speaker loop for command-line use:
go build -tags malgo -o /tmp/nanogo ./cmd/nanogo
export XAI_API_KEY=your-xai-key-here
export XAI_REALTIME_MODEL=grok-voice-think-fast-1.0
/tmp/nanogo --workspace /tmp/nanogo-workspace voice live \
--provider xai \
--child crossYou can also use the Makefile target:
make voice-liveThe live loop streams MacBook microphone audio to xAI, relies on xAI server_vad to detect spoken turns, and plays response audio through local speakers. macOS may prompt for microphone permission. Headphones are recommended to reduce speaker-to-microphone feedback.
By default, live sessions persist normalized events and transcripts under memory/voice/, but not raw microphone or speaker PCM. Raw PCM is saved only when explicitly requested:
/tmp/nanogo --workspace /tmp/nanogo-workspace voice live \
--provider xai \
--child cross \
--save-capture-pcm /tmp/nanogo-workspace/voice/capture.pcm \
--save-playback-pcm /tmp/nanogo-workspace/voice/playback.pcmmake voice-live-debugPhase 22 v2 adds the contracts needed for short browser micro-lessons where a child can practice a physical skill, such as a beginner yo-yo throw, and advance from deterministic evidence. For physical_skill objectives, a visual physical-performance pass can advance the lesson when that is the only configured evidence. Mixed, deep, and transfer objectives still require their configured reasoning, reflection, transfer, or parent-confirmation evidence.
Parent-facing benefits:
- Rough topics like "beginner yo-yo tricks" can produce curated source notes and a chain of small browser micro-lessons.
- Physical activities include child-facing safety setup before capture starts.
- Parent guide output discloses video segment provenance and flags model-inferred segments for verification.
- Captured clips stay local by default; persisted records use metadata, hashes, tombstones, and privacy-safe references.
Technical usage:
go build -o /tmp/nanogo ./cmd/nanogo
/tmp/nanogo --workspace /tmp/nanogo-workspace lessonfactory research \
--driver fake \
--topic "beginner yo-yo tricks" \
--child-age 7 \
--skill-type physicalManual Phase 22 smoke targets:
make test-22.1 # lesson_research sources.md smoke; requires OPENROUTER_API_KEY for openrouter driver
make test-22.3 # headed player smoke when agent-browser is installed
VISION_MODEL=openrouter/model-id make test-22.5test-22.5 requires both OPENROUTER_API_KEY and VISION_MODEL. The deterministic local gate remains:
make verify-local