Files
collection-claude-code-sour…/nano-claude-code/Update_README.MD
chauncygu 1d4ffa964d Update README.MD and add nano-claude-code v3.0 + original-source-code/src
- README.MD: add original-source-code and nano-claude-code sections, update
  overview table (4 subprojects), add v3.0 news entry, expand comparison table
  with memory/multi-agent/skills dimensions
- nano-claude-code v3.0: multi-agent package (multi_agent/), memory package
  (memory/), skill package (skill/) with built-in /commit and /review skills,
  context compression (compaction.py), tool registry plugin system, diff view,
  17 slash commands, 18 built-in tools, 101 tests (~5000 lines total)
- original-source-code/src: add raw TypeScript source tree (1884 files)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:26:29 -07:00

18 KiB

Nano Claude Code — Update Notes

This document describes three major feature additions to nano-claude-code: Multi-Agent, Memory, and Skill. Each feature is organized as a self-contained Python package, follows the same architectural pattern, and includes a backward-compatibility shim so existing code continues to work.


Architecture Overview

All three packages follow the same pattern:

feature/
  __init__.py   — public re-exports
  <core>.py     — data model, loading, business logic
  tools.py      — registers tools into the central tool_registry
  ...
feature.py      — backward-compat shim (re-exports from feature/)

The tool registry (tool_registry.py) is the central hub. Each feature's tools.py calls register_tool(ToolDef(...)) at import time. The top-level tools.py imports all three feature tool modules, triggering auto-registration.

The agent loop (agent.py) injects _depth and _system_prompt into the config dict on every call, so tool functions can read them via config.get(...).


1. Multi-Agent (multi_agent/)

What it does

Allows Claude to spawn sub-agents — nested agent loops that run concurrently in background threads. Sub-agents can share the parent's context or run in an isolated git worktree. The user can send follow-up messages to named background agents and retrieve their results.

Package structure

multi_agent/
  __init__.py       — re-exports AgentDefinition, SubAgentTask, SubAgentManager, etc.
  subagent.py       — core: AgentDefinition, SubAgentTask, SubAgentManager, worktree helpers
  tools.py          — registers: Agent, SendMessage, CheckAgentResult, ListAgentTasks, ListAgentTypes
subagent.py         — backward-compat shim

Key classes and functions

AgentDefinition (multi_agent/subagent.py)

@dataclass
class AgentDefinition:
    name: str
    description: str
    system_prompt: str   # prepended to base prompt for this agent type
    model: str           # "" = inherit from parent
    tools: list          # [] = all tools
    source: str          # "built-in" | "user" | "project"

Built-in agent types: general-purpose, coder, reviewer, researcher, tester

Custom agent definitions — place a .md file with YAML frontmatter in:

  • ~/.nano_claude/agents/<name>.md (user-level)
  • .nano_claude/agents/<name>.md (project-level, takes priority)

Frontmatter format:

---
name: my-agent
description: What this agent does
model: claude-opus-4-6
tools: [Read, Glob, Grep]
---
Extra system prompt instructions for this agent.

SubAgentManager (multi_agent/subagent.py)

  • spawn(prompt, config, agent_def, isolation, name, wait) — runs agent in thread pool
  • send_message(task_id_or_name, message) — enqueues message to a running background agent
  • get_result(task_id) — returns final text or status
  • list_tasks() — returns all SubAgentTask objects

Git worktree isolation: When isolation="worktree" is passed to Agent, a temporary git worktree is created on a fresh branch. The sub-agent works in isolation; if it makes no changes the worktree is cleaned up automatically.

Tools registered

Tool Description
Agent Spawn a sub-agent (sync or background with wait=false)
SendMessage Send a follow-up message to a named background agent
CheckAgentResult Poll status / retrieve result of a background agent
ListAgentTasks List all active and finished sub-agent tasks
ListAgentTypes List all available agent type definitions

Agent tool parameters

Agent(
    prompt="...",           # required — task description
    subagent_type="coder",  # optional — use a specialized agent
    isolation="worktree",   # optional — isolated git branch
    name="my-agent",        # optional — name for SendMessage later
    wait=False,             # optional — run in background
    model="...",            # optional — model override
)

How it was wired in

  1. multi_agent/subagent.py uses absolute imports (import agent as _agent_mod) because the project root is in sys.path when running from that directory.
  2. agent.py was updated to inject _system_prompt into config:
    config = {**config, "_depth": depth, "_system_prompt": system_prompt}
    
  3. tools.py (top-level) was updated to pass config through to the registry:
    return _registry_execute(name, inputs, cfg)
    
    and at the bottom:
    import multi_agent.tools as _multiagent_tools
    
  4. context.py system prompt template lists Agent, SendMessage, etc. under ## Multi-Agent.
  5. nano_claude.py /agents command calls get_agent_manager().list_tasks() and prints status/worktree info. A _print_background_notifications() function checks for newly completed background agents before each user prompt.

Files changed

File Change
multi_agent/__init__.py Created (re-exports)
multi_agent/subagent.py Created (moved + enhanced from subagent.py)
multi_agent/tools.py Created (tool registrations)
subagent.py Converted to backward-compat shim
agent.py Inject _system_prompt into config
tools.py Pass config to registry; import multi_agent.tools
context.py Add Multi-Agent section to system prompt
nano_claude.py /agents command; background notification; _tool_desc()
tests/test_subagent.py Update imports to multi_agent.subagent

2. Memory (memory/)

What it does

Provides persistent, file-based memory across sessions. Memories are stored as markdown files with YAML frontmatter. There are two scopes — user (global, ~/.nano_claude/memory/) and project (per-repo, .nano_claude/memory/). A MEMORY.md index is auto-rebuilt after every save/delete and injected into the system prompt so Claude knows what memories exist.

Package structure

memory/
  __init__.py   — re-exports all public symbols
  types.py      — MEMORY_TYPES, type descriptions, format guidance
  store.py      — MemoryEntry, save/load/delete/search, index rebuilding
  scan.py       — MemoryHeader, scan_memory_dir, age/freshness helpers
  context.py    — get_memory_context(), find_relevant_memories(), truncation
  tools.py      — registers: MemorySave, MemoryDelete, MemorySearch, MemoryList
memory.py       — backward-compat shim

Memory types

Defined in memory/types.py, mirrors the four types from Claude Code:

Type Purpose
user User's role, goals, preferences
feedback Corrections and confirmed approaches
project Ongoing work, decisions, deadlines
reference Pointers to external resources

Storage layout

~/.nano_claude/memory/
  MEMORY.md          ← auto-generated index (<=200 lines, <=25 KB)
  my_note.md
  feedback_testing.md
  ...

.nano_claude/memory/   ← project-local (relative to cwd)
  MEMORY.md
  ...

Each memory file format:

---
name: My Note
description: one-line description for relevance decisions
type: user
created: 2026-04-02
---

Memory content goes here.
**Why:** ...
**How to apply:** ...

Key API

memory/store.py

save_memory(entry: MemoryEntry, scope="user")   # save or update (same name = update)
delete_memory(name: str, scope="user")           # remove entry + rebuild index
load_entries(scope="user") -> list[MemoryEntry]  # load all entries for scope
load_index(scope="all") -> list[MemoryEntry]     # "all" merges user + project
search_memory(query: str, scope="all") -> list   # keyword search across content+name
get_index_content(scope="all") -> str            # raw MEMORY.md text

memory/scan.py

scan_memory_dir(mem_dir, scope) -> list[MemoryHeader]  # newest-first, capped at 200
scan_all_memories() -> list[MemoryHeader]              # user + project merged
memory_age_str(mtime_s) -> str          # "today" | "yesterday" | "N days ago"
memory_freshness_text(mtime_s) -> str   # staleness warning for memories >1 day old
format_memory_manifest(headers) -> str  # formatted list for display

memory/context.py

get_memory_context() -> str             # injected into system prompt
truncate_index_content(raw) -> str      # enforces <=200 lines / <=25 KB
find_relevant_memories(query, max_results=5, use_ai=False, config=None)

find_relevant_memories supports optional AI ranking: when use_ai=True it makes a small API call to rank candidates by relevance to the query.

Tools registered

Tool Parameters Description
MemorySave name, description, type, content, scope Save or update a memory
MemoryDelete name, scope Delete a memory by name
MemorySearch query, scope, use_ai, max_results Search by keyword (or AI)
MemoryList scope List all memories with age and metadata

Index truncation

The MEMORY.md index is truncated before being injected into the system prompt:

  • Hard limit: 200 lines (mirrors Claude Code's limit)
  • Byte limit: 25 000 bytes (mirrors Claude Code's limit)
  • A WARNING: line is appended when either limit is hit

How it was wired in

  1. memory/store.py exports USER_MEMORY_DIR and get_project_memory_dir as module-level names so tests can monkeypatch them cleanly.
  2. context.py (system prompt builder) calls get_memory_context() at the end of build_system_prompt() and appends the result.
  3. tools.py (top-level) adds:
    import memory.tools as _memory_tools
    
  4. memory.py (top-level) is now a shim:
    from memory.store import MemoryEntry, save_memory, ...
    from memory.context import get_memory_context
    
  5. nano_claude.py /memory command uses scan_all_memories() to display a mtime-sorted list with freshness warnings.

Files changed

File Change
memory/__init__.py Created (re-exports)
memory/types.py Created (MEMORY_TYPES, descriptions, format guidance)
memory/store.py Created (replaced top-level memory.py logic)
memory/scan.py Created (MemoryHeader, age/freshness, manifest)
memory/context.py Created (context injection, truncation, AI search)
memory/tools.py Created (MemorySave, MemoryDelete, MemorySearch, MemoryList)
memory.py Converted to backward-compat shim
tools.py Import memory.tools
context.py Call get_memory_context() in build_system_prompt()
nano_claude.py /memory command uses scan_all_memories()
tests/test_memory.py Completely rewritten (101 tests total)

3. Skill (skill/)

What it does

Skills are reusable prompt templates stored as markdown files. A user types /commit or /review pr-123 in the REPL and the skill's prompt (with arguments substituted) is injected into the conversation. Skills can run inline (current conversation context) or forked (isolated sub-agent). Two built-in skills (/commit, /review) are registered programmatically.

Package structure

skill/
  __init__.py   — re-exports all public symbols; imports builtin to register them
  loader.py     — SkillDef dataclass, file parsing, load_skills, find_skill, substitute_arguments
  builtin.py    — built-in skills: /commit, /review
  executor.py   — execute_skill() (inline or forked)
  tools.py      — registers: Skill, SkillList
skills.py       — backward-compat shim

Skill file format

Place .md files in:

  • ~/.nano_claude/skills/<name>.md (user-level)
  • .nano_claude/skills/<name>.md (project-level, takes priority)
---
name: deploy
description: Deploy to an environment
triggers: [/deploy]
allowed-tools: [Bash, Read]
when_to_use: Use when the user wants to deploy. Examples: '/deploy staging v1.2'
argument-hint: [env] [version]
arguments: [env, version]
context: inline
---

Deploy $VERSION to $ENV.

Full args provided: $ARGUMENTS

Frontmatter fields

Field Default Description
name required Skill identifier
description "" One-line description shown in /skills
triggers [/<name>] Slash commands or phrases that activate this skill
allowed-tools / tools [] Tools the skill is allowed to use
when_to_use "" Guidance for when Claude should auto-invoke
argument-hint "" Hint shown in /skills list, e.g. [branch] [desc]
arguments [] Named argument list for $ARG_NAME substitution
model "" Model override (fork context only)
user-invocable true Show in /skills list
context inline inline = current conversation, fork = isolated sub-agent

Argument substitution

substitute_arguments(prompt, args, arg_names) in skill/loader.py:

  • $ARGUMENTS → the full raw args string
  • $ARG_NAME → positional substitution (first word → first arg name, etc.)
  • Missing args become empty strings
prompt:    "Deploy $VERSION to $ENV. Full: $ARGUMENTS"
args:      "1.0 staging"
arg_names: ["env", "version"]

result:    "Deploy staging to 1.0. Full: 1.0 staging"

Execution modes

Inline (context: inline, default):

  • Skill prompt is injected into the current AgentState
  • History is shared — the user can see and continue the conversation

Fork (context: fork):

  • A fresh AgentState is created (no shared history)
  • Optional model and allowed-tools overrides are applied
  • Good for self-contained tasks that don't need mid-process user input

Built-in skills

Defined in skill/builtin.py and registered via register_builtin_skill():

Trigger Name Description
/commit commit Review staged changes and create a well-structured git commit
/review, /review-pr review Review code or PR diff with structured feedback

Project-level skill files with the same name override built-ins.

Tools registered

Tool Parameters Description
Skill name, args Invoke a skill by name from inside a conversation
SkillList List all available skills with triggers and metadata

Priority order

When multiple skill sources define the same name, the highest priority wins:

builtin  <  user (~/.nano_claude/skills/)  <  project (.nano_claude/skills/)

REPL usage

/commit                          # run built-in commit skill
/review 123                      # review PR #123 (args = "123")
/deploy staging 2.1.0            # custom skill with named args
/skills                          # list all skills

The /skills command output includes source label, triggers, argument hint, and the first 80 chars of when_to_use per skill.

How it was wired in

  1. skill/__init__.py imports skill.builtin which calls register_builtin_skill() for each built-in — just importing the package registers them.
  2. tools.py (top-level) adds:
    import skill.tools as _skill_tools
    
  3. skills.py (top-level) becomes a shim re-exporting from skill/.
  4. context.py adds a ## Skills section listing Skill and SkillList.
  5. nano_claude.py:
    • cmd_skills imports from skill, shows when_to_use and source label
    • handle_slash imports find_skill from skill; returns (skill, args) tuple
    • REPL loop calls substitute_arguments before building the injected message

Files changed

File Change
skill/__init__.py Created (re-exports; imports builtin)
skill/loader.py Created (SkillDef, parse, load, find, substitute)
skill/builtin.py Created (/commit, /review built-ins)
skill/executor.py Created (inline + fork execution)
skill/tools.py Created (Skill, SkillList tool registration)
skills.py Converted to backward-compat shim
tools.py Import skill.tools
context.py Add Skills section to system prompt
nano_claude.py cmd_skills, handle_slash, REPL loop updated
tests/test_skills.py Rewritten (22 tests; patches skill.loader)

How to add custom agents, memories, and skills

Custom agent type

Create ~/.nano_claude/agents/myagent.md:

---
name: myagent
description: Does specialized work
model: claude-haiku-4-5-20251001
tools: [Read, Grep, Bash]
---
You are specialized in X. Focus on Y. Never do Z.

Then use: Agent(prompt="...", subagent_type="myagent")

Custom memory

Use the REPL MemorySave tool or write a file directly to ~/.nano_claude/memory/my_note.md with frontmatter:

---
name: my note
description: short description
type: feedback
created: 2026-04-02
---
Memory content here.

Custom skill

Create ~/.nano_claude/skills/myskill.md (user-level) or .nano_claude/skills/myskill.md (project-level):

---
name: myskill
description: Does something useful
triggers: [/myskill]
arguments: [target]
argument-hint: [target]
when_to_use: Use when the user wants to do X with a target.
---

Do something useful with $TARGET.

Full context: $ARGUMENTS

Then invoke with /myskill some-target.


Running tests

cd nano-claude-code

# All tests
python -m pytest tests/ -v

# Per-feature
python -m pytest tests/test_subagent.py -v   # multi-agent
python -m pytest tests/test_memory.py   -v   # memory
python -m pytest tests/test_skills.py   -v   # skills

Total: 101 tests, all passing. Each feature's tests use monkeypatch to redirect file system paths to tmp_path so no real ~/.nano_claude/ directories are touched during testing.