Skip to content

Building PyNanoClaw's Memory Subsystem

Before you start

Prerequisite: Lesson 24, "File-based and vector memory in practice: two labs, two implementations" — you should have working file-memory and vector-memory implementations and know why the metadata filter, not the embedding, does most of the retrieval work. After this lesson, you can: explain which specific patterns PyNanoClaw keeps from NanoClaw and which it deliberately drops, and read the formal Memory protocol well enough to know that file, vector, and hybrid backends are three implementations of one interface, not three separate systems.

Restart the process right now, on everything built through Week 3, and every agent forgets the user's name, the project it was mid-task on, and the preference it was told twice already. That's the gap this lesson closes: not a new reasoning trick, but the piece that makes an agent remember tomorrow what happened today.

Three weeks of pieces come together starting now. Week 1 built a reasoning brain with managed context. Week 2 gave it hands that call tools across five paradigms. Week 3 gave it a planner that thinks before acting. This week, starting with this lesson, those pieces get integrated into one codebase — PyNanoClaw — beginning with the piece that survives a session: memory.

The claw idea, briefly restated

Karpathy's "claws" framing, used across OpenClaw and NanoClaw, describes an always-on personal agent: it runs continuously rather than turn-by-turn, wakes on triggers (messages, schedules, file events), maintains one persistent identity across sessions, reaches out to tools and other agents, and — the subject of this entire week — builds knowledge across sessions rather than starting fresh each time. Its memory is files; its tools are code; nothing about its state is hidden behind an opaque service. A claw is not a stateless chatbot with better branding. It is closer to an employee who comes back tomorrow and remembers what happened yesterday.

NanoClaw, reverse-engineered

PyNanoClaw is not invented from a blank page — it is NanoClaw's architecture deliberately reverse-engineered and rewritten for this course's stack, and every rewrite decision below (which patterns to keep, which to drop, and the formal Memory protocol itself) is Rod Rivera's own design call, not an arbitrary port. NanoClaw itself (github.com/qwibitai/nanoclaw) is a TypeScript/Node.js project with roughly 27,000 GitHub stars as of April 2026, pitched as a lightweight alternative to OpenClaw. Its README states the philosophy directly: "Small enough to understand. One process, a few source files and no microservices. ~15 source files, a single Node.js process, and real OS-level container isolation."

Its core components: a single Node.js orchestrator process; per-group containerized Claude agents running in Docker or Apple Container; seven channels — WhatsApp, Telegram, Slack, Discord, Gmail, Signal, Matrix; skills implemented as git branches merged into a fork via three-way merge; filesystem-based memory, one .claude/CLAUDE.md per group; and a credential gateway so containers never see real API keys directly.

Single Node.js process (orchestrator) polls SQLite for new messages, per-group queue, IPC via filesystem | Containerized Claude agents (one per group) isolated filesystem, mounted data/sessions/{group}/ + .claude/, read-only project-root mount, cannot see other groups' history | OneCLI gateway (credential proxy) real credentials never enter containers; injected at the gateway

What transfers, and what doesn't

Seven patterns are worth keeping outright, and the reasons are specific rather than aesthetic: per-group filesystem isolation is the best available multi-tenant pattern and trivially auditable; CLAUDE.md as project memory is inspectable and human-editable; skills directories with SKILL.md are self-contained composable units; the credential gateway keeps real secrets out of untrusted execution; a single process with no microservices stays operationally simple; mount allowlists make it hard to accidentally expose a sensitive path; and schema-versioned state files make upgrades safe. These translate cleanly to Python, which is exactly why they're worth borrowing rather than the whole system.

What changes is a longer, more specific list, and it matters that the two lists don't overlap by accident:

DecisionNanoClawPyNanoClaw
LanguageTypeScript / Node.jsPython
LLM providerClaude Agent SDK (Claude only)OpenAI-compatible (Nebius default)
ModelsClaude Sonnet 4.5DeepSeek R1 (planner) + Llama 3.3 70B (executor) + Qwen3 embedder
ToolsInherited from Claude CodeExplicit Python registry (Week 2 patterns)
PlanningReactive (Claude Code's default)Planner-Executor (Week 3)
MemoryImplicit .claude/ directoryExplicit subsystem with a CRUD API
ContainersRequired (Docker / Apple Container)Optional — subprocess plus directory isolation by default
Skills as git branchesYesNo — directories only, a simpler model
Channels7+ supportedCLI plus Telegram; the rest as student exercises

"Inspired by NanoClaw" undersells what actually happened. This is not a loose reimagining in the same spirit — it's nine specific keep-or-drop decisions, made on their own merits, and the two patterns that transfer almost unchanged (the memory conventions and the credential-gateway security model) transfer because they were judged independently good, not because everything else did too.

Files changed
pynanoclaw/memory/base.py
pynanoclaw/memory/file_memory.py
pynanoclaw/memory/vector_memory.py
pynanoclaw/memory/hybrid.py
pynanoclaw/memory/extractor.py
pynanoclaw/memory/reflection.py
pynanoclaw/memory/consolidator.py
pynanoclaw/ ├── core/ # Agent class, event loop, session ├── planner/ # Week 3's Planner-Executor (next week) ├── executor/ # Tool dispatch (next week) ├── memory/ # <- built starting today │ ├── base.py # Memory protocol │ ├── file_memory.py # CLAUDE.md backend │ ├── vector_memory.py# Chroma backend │ ├── hybrid.py # File + vector combined │ ├── extractor.py # Fact extraction │ ├── reflection.py # Reflexion pattern │ └── consolidator.py # Sleep-time / background ├── tools/ # Tool registry (Week 2 patterns) ├── channels/ # CLI, Telegram (next week) ├── models/ # Nebius client wrappers ├── security/ # Isolation, credential gateway └── observability/ # Structured logging, traces

That's a roughly 6,000-line Python codebase in total, with the memory module the only piece started this week — the planner, executor, deployment, and evals modules follow next week, per the course's own stated roadmap.

The Memory protocol

The goals for this subsystem, stated as design constraints rather than aspirations: inspectable — open the memory directory in any text editor and see everything; pluggable — files by default, a vector store as an opt-in scaling layer, both at once via a hybrid backend; multi-tenant safe — isolation by directory, per group; audit-friendly — every memory carries a source, a timestamp, and a trust score; and cheap — negligible embedding cost, local storage.

python
class Memory(Protocol):
    async def write(self, content: str, *, kind: str, metadata: dict) -> str: ...
    async def read(self, memory_id: str) -> MemoryRecord: ...
    async def search(self, query: str, *, k: int = 10, filters: dict = None) -> list: ...
    async def update(self, memory_id: str, new_content: str) -> None: ...
    async def delete(self, memory_id: str) -> None: ...
    async def consolidate(self) -> int: ...
    async def reflect(self, context: dict) -> str | None: ...

One protocol, three implementations — file, vector, hybrid — swappable by configuration alone. This is the direct payoff of getting the interface right before the implementation: the agent code that calls memory.search(...) never needs to know or care which backend is actually running underneath.

One protocol, three swappable backends, one directory convention

The directory layout convention

data/sessions/{group_id}/ ├── memory/ │ ├── CLAUDE.md # Project rules + identity │ ├── user_profile.md # Stable user facts │ ├── preferences.md # User preferences (hot) │ ├── episodes/ # Session logs │ ├── entities/ # One file per named entity │ ├── procedures/ # How-to memory │ └── reflections/ # Reflexion memories ├── vectors.chroma/ # Chroma persistent dir (optional) └── logs/ └── traces.jsonl # Audit trail

The structure isn't arbitrary organization — each piece maps to a real access pattern. CLAUDE.md and user_profile.md are always in context, the same "core memory" zone this course has discussed since Type 6's hierarchical model. episodes/, entities/, procedures/, and reflections/ are indexed in the vector store and retrieved on demand rather than always loaded. And the .claude/ compatibility is deliberate: a student can point Claude Code itself at a session directory and it works without translation, because the convention was borrowed rather than reinvented.

Quick check — A student describes PyNanoClaw as 'basically NanoClaw, just in Python instead of TypeScript.' What does this lesson's decision table say is missing from that description?

What to carry into the next lesson

The blueprint is complete: the protocol, the directory layout, the architectural lineage from NanoClaw. Nothing has been attacked yet, and nothing has actually learned from a failure yet. The next lesson builds against this exact protocol — a Reflexion-augmented planner that changes its own plan after a failure, a live memory-poisoning attack and its defense, and the synthesis lab that implements HybridMemory for real.

Continue to Lesson 26

Lab 4 shows memory changing a planner's actual output. Lab 5 poisons that memory and then defends it. Lab 6 builds the HybridMemory class this lesson's protocol promised.

Have a question about this lesson?

Reply here and it goes straight to Rod. Same as replying to one of his emails.