Lemon AI Self-Evolving Memory System Architecture: A Technical Deep Dive
Lemon AI implements a self-evolving memory architecture that continuously refines agent behavior by persisting execution traces, reflection feedback, and knowledge items across layered JSON storage systems.
The hexdocom/lemonai repository introduces a modular memory stack designed to overcome the limitations of static LLM context windows. Unlike traditional agent systems that lose state between sessions, this self-evolving architecture combines per-task message logs, long-term knowledge storage, and automated runtime hooks that capture every action outcome. The implementation relies on lightweight JSON-file stores and a reflection engine that feeds evaluation results back into subsequent reasoning cycles.
Architecture Overview
The Lemon AI self-evolving memory system operates through five distinct layers, each responsible for specific persistence and retrieval functions. The following table maps each layer to its core implementation file and role in the continuous learning loop:
| Layer | Responsibility | Core Implementation | How it fits in the loop |
|---|---|---|---|
| Per-task message memory | Stores chronological dialogue between the LLM and user, plus metadata for each action (e.g., whether it should be memorized). | LocalMemory – stores a JSON file per task in src/agent/memory/LocalMemory.js |
Each iteration of code-act (completeCodeAct) loads the file, appends a new message via memory.addMessage, and reads the full history via memory.getMessages to feed the next LLM prompt. |
| Long-term knowledge storage | Persists "memories" that survive across conversations (e.g., discovered snippets, learned facts). Supports CRUD and full-text search. | MemoryStorage (abstract) in src/knowledge/MemoryStorage.js → FileStorage (file-based implementation) in src/knowledge/FileStorage.js |
When a tool marks an action as memorized, DockerRuntime.handle_memory writes a concise summary into the per-task LocalMemory. Higher-level agents can later move interesting entries into FileStorage for reuse. |
| Reflection engine | Evaluates the result of an action using either a simple status check or a secondary LLM call, then returns structured XML that may trigger new actions. | reflection in src/agent/reflection/index.js |
After a tool finishes, code-act calls reflection(requirement, action_result, conversation_id). The returned status and comments are logged back into LocalMemory and fed into the next LLM thinking round. |
| Runtime / Execution layer | Executes concrete tool actions (write files, run terminals, call Docker containers) and forwards their outcomes to memory. | DockerRuntime in src/runtime/DockerRuntime.js (also LocalRuntime for non-Docker env) |
DockerRuntime.execute_action runs the tool, then calls handle_memory(result, action, context.memory). The helper builds a meta object (including action_memory) and stores it via memory.addMessage. |
| Code-Act orchestrator | The main loop that drives LLM reasoning, parses XML actions, executes them, and decides when to stop or pause. | completeCodeAct in src/agent/code-act/code-act.js |
The orchestrator repeatedly: (1) think → (2) parse → (3) execute → (4) reflect → (5) log. Each cycle enriches the per-task memory, making the next LLM call "self-evolving". |
Data Flow in a Single Iteration
Understanding the Lemon AI self-evolving memory architecture requires tracing how data moves through the system during one complete execution cycle. The completeCodeAct orchestrator in src/agent/code-act/code-act.js drives this process through five distinct phases:
-
Think – The
thinking(requirement, context)function creates a prompt that includes the currentmemory(full message history loaded viamemory.getMessages) and any priorreflectionfeedback. -
Parse – The
resolveActionsutility turns the LLM's XML output into a structuredactionobject containing the tool type and parameters. -
Execute –
runtime.execute_action(action, context, task.id)runs the tool within the Docker container or local environment. -
Handle Memory – Inside
DockerRuntime.handle_memory(defined insrc/runtime/DockerRuntime.js), the system:- Detects if the tool belongs to the
memorized_typeset (read_file,write_code,terminal_run). - Calls
tool.resolveMemory(if provided) to generate a concise XML snippet (action_memory). - Invokes
memory.addMessage('user', content, action.type, memorized, meta)to persist the trace.
- Detects if the tool belongs to the
-
Reflect – The
reflection(requirement, action_result, conversation_id)function (fromsrc/agent/reflection/index.js) evaluates the outcome using either heuristic status checks or secondary LLM calls, returning structured XML containing astatusfield andcomments. -
Log Reflection – If reflection indicates failure, the orchestrator writes the comment back into
LocalMemoryviamemory.addMessage("user", comments)and may trigger a retry loop.
Because memory persists as a JSON file on disk, the next loop iteration loads the same file, automatically "remembering" everything that occurred earlier. This persistence mechanism forms the core of the self-evolving capability.
Implementation Examples
The following code examples demonstrate how to interact with the memory system components directly, illustrating the practical implementation of the Lemon AI self-evolving memory architecture.
Using LocalMemory for Per-Task Persistence
The LocalMemory class in src/agent/memory/LocalMemory.js provides the primary interface for storing conversation history and action metadata within a single task context.
// Create a task-scoped memory (task id = 42, sub-folder = "a1b2c3")
const LocalMemory = require("@src/agent/memory/LocalMemory");
const memory = new LocalMemory({ memory_dir: "a1b2c3", key: 42 });
// Add a user prompt (memorized = true so it will be stored as action_memory)
await memory.addMessage(
"user",
"Create a file named hello.js with console.log('Hi')",
"write_code",
true,
{ action: { type: "write_code", params: { path: "hello.js" } } }
);
// Retrieve the whole conversation history
const history = await memory.getMessages();
console.log(history);
This example demonstrates how LocalMemory.addMessage persists structured data including the action type, memorization flag, and metadata, while LocalMemory.getMessages retrieves the complete history for context window construction.
Storing Long-Term Knowledge with FileStorage
For cross-session persistence, the FileStorage implementation in src/knowledge/FileStorage.js extends the abstract MemoryStorage interface to provide CRUD operations and full-text search capabilities.
const FileStorage = require("@src/knowledge/FileStorage");
// Instantiate a knowledge store for the "coding" domain
const knowledgeStore = new FileStorage({ directory: "coding" });
const mem = {
content: "The Node.js `fs` module provides synchronous and asynchronous file APIs.",
metadata: { tags: ["node", "fs"], source: "official docs" }
};
// Persist the memory (auto-generates an UUID)
const saved = await knowledgeStore.save(mem);
console.log("Saved memory id:", saved.id);
// Search for all memories containing the word "file"
const results = await knowledgeStore.search("file", { limit: 5 });
console.log("Search hits:", results);
The FileStorage.save method automatically generates UUIDs for knowledge items, while FileStorage.search enables retrieval of relevant historical context based on keyword matching.
Runtime Automatic Logging
The DockerRuntime class in src/runtime/DockerRuntime.js demonstrates how execution results automatically propagate to memory without manual intervention.
// Inside a DockerRuntime execution (simplified)
async function demo() {
const runtime = new DockerRuntime({ user_id: "123" });
await runtime.connect_container(); // ensures docker_host is available
const action = { type: "write_code", params: { path: "test.txt", content: "Hello" } };
const context = { conversation_id: "conv_001", memory: new LocalMemory({ key: "conv_001" }) };
const result = await runtime.execute_action(action, context, "task-99");
// After execution, result is already saved to memory:
// - result.content => written file content
// - context.memory now contains a 'user' message with `action_memory`
}
The DockerRuntime.execute_action method internally calls handle_memory, which constructs metadata objects and persists them via memory.addMessage, ensuring complete traceability of agent actions.
Reflection Feedback Integration
The reflection mechanism in src/agent/reflection/index.js closes the learning loop by evaluating action outcomes and feeding assessments back into memory.
const reflect = require("@src/agent/reflection");
const requirement = "Generate a Python script that prints Fibonacci numbers.";
const fakeResult = { status: "success", content: "def fib():\n ..." };
const { status, comments } = await reflect(requirement, fakeResult, "conv_002");
if (status === "success") {
console.log("Reflection says everything is fine.");
} else {
console.error("Reflection error:", comments);
}
When reflection detects failures, the orchestrator writes error comments back to LocalMemory via memory.addMessage, enabling the agent to learn from mistakes in subsequent iterations.
Summary
- Lemon AI's self-evolving memory system combines per-task JSON persistence with long-term knowledge storage to create continuous learning loops that transcend static context windows.
LocalMemory(src/agent/memory/LocalMemory.js) provides the backbone for task-scoped conversation history, storing chronological dialogue and action metadata in individual JSON files per task.FileStorage(src/knowledge/FileStorage.js) implements the abstractMemoryStorageinterface for cross-session knowledge retention, supporting CRUD operations and full-text search across conversation boundaries.DockerRuntime(src/runtime/DockerRuntime.js) automatically logs execution traces throughhandle_memory, ensuring all tool interactions persist without manual intervention viamemory.addMessage.reflection(src/agent/reflection/index.js) closes the feedback loop by evaluating action outcomes and feeding structured assessments back into the memory stream for iterative improvement.- The code-act orchestrator (
completeCodeActinsrc/agent/code-act/code-act.js) drives the think-parse-execute-reflect-log cycle that makes the system truly self-evolving.
Frequently Asked Questions
How does Lemon AI decide what to store in long-term memory versus per-task memory?
The system distinguishes between transient conversation context and durable knowledge through the memorized flag and tool type detection. In src/runtime/DockerRuntime.js, the handle_memory method checks if a tool belongs to the memorized_type set (including read_file, write_code, and terminal_run). When memorized is true, the runtime calls tool.resolveMemory to generate a concise XML snippet (action_memory) and persists it via memory.addMessage. Higher-level agents can later promote significant entries from per-task storage to FileStorage for cross-session reuse.
What file format does Lemon AI use for persistent memory storage?
Lemon AI uses lightweight JSON files as its primary persistence mechanism. The LocalMemory class in src/agent/memory/LocalMemory.js creates one JSON file per task (identified by a unique key), storing the chronological message history as an array of objects. Similarly, FileStorage in src/knowledge/FileStorage.js persists knowledge items as individual JSON files on disk, enabling simple CRUD operations and full-text search without external database dependencies.
How does the reflection engine contribute to the self-evolving capability?
The reflection engine in src/agent/reflection/index.js serves as the critical feedback mechanism that transforms raw execution logs into learning signals. After each tool execution, the reflection function evaluates the action_result against the original requirement using either heuristic status checks or secondary LLM calls. It returns structured XML containing a status field and comments. The code-act orchestrator (completeCodeAct in src/agent/code-act/code-act.js) then writes these assessments back into LocalMemory via memory.addMessage, ensuring that subsequent LLM prompts include prior failure analysis and corrective context, enabling iterative improvement across conversation turns.
Can the memory system operate without Docker?
Yes, the memory architecture is runtime-agnostic regarding the execution environment. While DockerRuntime in src/runtime/DockerRuntime.js provides containerized execution with automatic memory handling, the system includes LocalRuntime for non-Docker environments. Both runtime implementations interact with the same LocalMemory interface (src/agent/memory/LocalMemory.js) through the handle_memory pattern, ensuring that execution traces persist regardless of whether tools run inside Docker containers or directly on the host system. The abstract MemoryStorage interface (src/knowledge/MemoryStorage.js) further ensures that long-term knowledge storage can swap between file-based, database, or vector store implementations without changing the core memory logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →