Building PyNanoClaw's Memory Subsystem
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:
| Decision | NanoClaw | PyNanoClaw |
|---|---|---|
| Language | TypeScript / Node.js | Python |
| LLM provider | Claude Agent SDK (Claude only) | OpenAI-compatible (Nebius default) |
| Models | Claude Sonnet 4.5 | DeepSeek R1 (planner) + Llama 3.3 70B (executor) + Qwen3 embedder |
| Tools | Inherited from Claude Code | Explicit Python registry (Week 2 patterns) |
| Planning | Reactive (Claude Code's default) | Planner-Executor (Week 3) |
| Memory | Implicit .claude/ directory | Explicit subsystem with a CRUD API |
| Containers | Required (Docker / Apple Container) | Optional — subprocess plus directory isolation by default |
| Skills as git branches | Yes | No — directories only, a simpler model |
| Channels | 7+ supported | CLI 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.
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.
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.
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.
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.
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.
Reply here and it goes straight to Rod. Same as replying to one of his emails.