ZeroClaw Framework Adapter

Translates between ZeroClaw's trait-based memory system and the Agent Life Format (ALF). This adapter bridges ZeroClaw's Rust-native storage backends — SQLite with hybrid search and Markdown with lifecycle hygiene — to ALF's portable archive format.


1. How ZeroClaw Stores Memory

Architecture Overview

ZeroClaw's memory system is built on a trait-based abstraction — every memory operation (store, recall, get, forget, count) passes through a Memory trait interface, and the backing implementation is selected by configuration. Unlike OpenClaw's file-first philosophy where Markdown is the source of truth and SQLite is a derived index, ZeroClaw treats its configured backend as the authoritative store. When using the SQLite backend, the database is the memory — not a cache of files on disk.

This is the fundamental architectural difference from OpenClaw and has significant implications for the adapter:

AspectOpenClawZeroClaw
Source of truthMarkdown filesConfigured backend (SQLite or Markdown)
SQLite roleDerived search indexPrimary storage
Record schemaFree-form Markdown sectionsStructured MemoryEntry (key/content/category/timestamp/score)
Record IDsNone (adapter generates them)UUID per entry
CategoriesImplicit (file path)Explicit enum: Core, Daily, Conversation, Custom(String)

ZeroClaw ships several memory backends, selected via [memory].backend:

A further backend, lucid, bridges to an external Lucid process for advanced search.

Adapter scope: the agent-life ZeroClaw adapter extracts structured memory from the sqlite and markdown backends only. The postgres, qdrant, lucid, and none backends are not yet supported — an export of such an install produces no memory records (the other ALF layers are still exported). See Accepted Limitations.

The Memory Trait

The Memory trait (src/memory/traits.rs) defines the interface all backends implement:

#[async_trait]
pub trait Memory: Send + Sync {
    fn name(&self) -> &str;
    async fn store(&self, key: &str, content: &str, category: MemoryCategory) -> Result<()>;
    async fn recall(&self, query: &str, limit: usize) -> Result<Vec<MemoryEntry>>;
    async fn get(&self, key: &str) -> Result<Option<MemoryEntry>>;
    async fn forget(&self, key: &str) -> Result<bool>;
    async fn count(&self) -> Result<usize>;
    async fn list(&self, limit: usize, offset: usize) -> Result<Vec<MemoryEntry>>;
}

The trait is Send + Sync for safe sharing across async Tokio tasks. All six data methods are async — the SQLite backend wraps rusqlite calls in tokio::task::spawn_blocking to avoid blocking the Tokio runtime.

MemoryEntry and MemoryCategory

Every stored memory is a MemoryEntry:

pub struct MemoryEntry {
    pub id: String,          // UUID string
    pub key: String,         // Human-readable key (e.g., "user_preference_timezone")
    pub content: String,     // The memory content (free-form text)
    pub category: MemoryCategory,
    pub timestamp: String,   // RFC 3339 timestamp
    pub score: f64,          // Relevance score (populated during recall)
}

MemoryCategory is an enum with four variants:

pub enum MemoryCategory {
    Core,           // Durable facts, preferences, identity info
    Daily,          // Day-to-day observations, session context
    Conversation,   // Auto-saved conversation turns
    Custom(String), // User-defined category
}

The to_memory_category() helper maps freeform string labels (from tool calls) to these variants. The Display implementation renders them as stable lowercase strings ("core", "daily", "conversation", "custom:my_label").

SQLite Backend

Verified against ZeroClaw 0.8.2 (memories schema_version 3). The default backend (src/memory/sqlite.rs) stores everything in a single SQLite database named brain.db, located at ~/.zeroclaw/data/memory/brain.db — under the install root's data/ tree, not inside a workspace/ subdirectory. This one database is shared by every agent in the install and partitioned by an agent_id column (see Workspace Layout). Tables:

TablePurpose
agentsOne row per agent: id, alias, created_at. memories.agent_id is a NOT NULL foreign key into it.
memoriesPrimary storage: id, key, content, category, embedding (BLOB, nullable), created_at, updated_at, session_id, namespace, importance, superseded_by, agent_id. UNIQUE(agent_id, key). There is no timestamp column — timestamps are created_at/updated_at (RFC 3339).
memories_ftsFTS5 shadow table over key/content, maintained by the memories_ai/ad/au triggers — never written directly.
embedding_cacheCache keyed by content_hash.
schema_versionPer-component migration versions (memories = 3 on 0.8.2).

The other databases under data/ (data/sessions/sessions.db, data/sessions/acp-sessions.db, data/control_plane.db) are operational state, not agent memory, and are out of scope for the ALF adapter.

The store operation:

  1. Generates a UUID for id
  2. Computes embedding via the configured EmbeddingProvider (or stores NULL)
  3. Inserts into memories table
  4. FTS5 virtual table auto-indexes the content

The recall operation:

  1. Embeds the query text
  2. Runs vector cosine similarity across all embedding BLOBs
  3. Runs FTS5 BM25 keyword search
  4. Merges results using weighted fusion (default: 70% vector, 30% keyword)
  5. Returns top-N MemoryEntry items with populated score

Markdown Backend

The markdown backend (src/memory/markdown.rs) stores memories as plain Markdown files in the workspace:

~/.zeroclaw/workspace/
└── memory/
    ├── 2026-02-15.md         # Daily file
    ├── 2026-02-16.md         # Daily file
    ├── session_a1b2c3d4.md   # Session file
    └── archive/              # Hygiene: archived files
        ├── 2026-02-08.md
        └── session_old123.md

Two file types with distinct lifecycles:

The hygiene system runs as part of the daemon's maintenance loop:

The markdown backend does not support vector search or embeddings — recall is substring/keyword matching only.

Memory Snapshot & Hydration

Independently of the configured backend, ZeroClaw maintains a human-readable, Git-friendly mirror of its Core-category memories at ~/.zeroclaw/workspace/MEMORY_SNAPSHOT.md (src/memory/snapshot.rs). Two halves:

This is ZeroClaw's own portable export of durable memory — the closest native analogue to what ALF does for the full five-layer state. It is therefore relevant to round-trip and restore: a snapshot is sufficient to reconstruct Core memory even with no database present.

Memory Tools

ZeroClaw exposes memory to the agent through built-in tools:

ToolDescription
memory_storeSave content under a key with a category
memory_recallSemantic/keyword search returning ranked results
memory_forgetDelete a memory entry by key

These tools are registered automatically in the agent loop. The agent decides when to use them based on conversation context and system prompt instructions.

Auto-Save Behavior

When memory.auto_save = true (default), ZeroClaw automatically stores:

These per-turn keys are deliberately excluded from ZeroClaw's own context assembly (the is_user_autosave_key detector skips them) to prevent exponential context growth, even though they remain stored and searchable.

Workspace Layout

ZeroClaw's workspace lives at ~/.zeroclaw/ by default:

~/.zeroclaw/
├── config.toml                 # TOML configuration (all settings)
├── .secret_key                 # ChaCha20-Poly1305 encryption key (0600)
├── workspace/
│   ├── SOUL.md                 # Agent persona (OpenClaw-compatible)
│   ├── IDENTITY.md             # Agent identity (OpenClaw-compatible)
│   ├── AGENTS.md               # Operating instructions
│   ├── USER.md                 # User profile
│   ├── HEARTBEAT.md            # Periodic task checklist
│   ├── TOOLS.md                # Tool notes
│   ├── identity.json           # AIEOS identity (if format = "aieos")
│   ├── MEMORY_SNAPSHOT.md       # Core memories mirrored here (Git/recovery)
│   └── memory/                 # Memory store — both backends live here
│       ├── brain.db            # SQLite backend (if enabled)
│       ├── YYYY-MM-DD.md       # Markdown backend daily files (if enabled)
│       ├── session_*.md        # Markdown backend session files
│       └── archive/            # Hygiene: archived markdown files
├── state/                      # Runtime state
│   └── wa-session.db           # WhatsApp Web session (if enabled)
└── skills/                     # Installed skills

Key differences from OpenClaw workspace:

Unverified pending confirmation on 0.8.2: the MEMORY_SNAPSHOT.md mirror / hydration path (below), and the postgres/qdrant/lucid backends — the ALF adapter does not depend on any of these.


2. How Memory Is Amended

Agent-Initiated Writes via Tools

The primary mechanism. During conversation, the agent calls memory_store with a key, content, and category. The memory backend persists it immediately. Example tool call:

{
  "tool": "memory_store",
  "key": "user_timezone",
  "content": "User is in America/Los_Angeles (Pacific Time)",
  "category": "core"
}

Auto-Save (Conversation Capture)

When auto_save = true, every user message and assistant response longer than 20 characters is automatically stored, under key prefixes user_msg_ and assistant_resp_ respectively. This happens transparently — the agent doesn't trigger it. ZeroClaw filters these per-turn keys out of its own context assembly to avoid runaway context growth.

Auto-Recall (Context Injection)

Before each agent response, ZeroClaw automatically searches memory for entries relevant to the current message and injects them into the system prompt as context. This is controlled by memory.auto_recall (defaults to true when a backend is configured). The agent receives retrieved memories without needing to explicitly call memory_recall.

Memory Hygiene (Markdown Backend)

The markdown backend runs a hygiene loop when ZeroClaw operates in daemon mode:

The SQLite backend does not have automatic hygiene — entries persist until explicitly forgotten via memory_forget or memory clear.

Migration from OpenClaw

ZeroClaw includes a built-in migration command:

zeroclaw migrate openclaw --dry-run    # Preview
zeroclaw migrate openclaw              # Execute

This reads OpenClaw's Markdown memory files (MEMORY.md, memory/**/*.md) and imports them as ZeroClaw MemoryEntry records. The migration maps OpenClaw content to ZeroClaw categories:

Important limitation: migration imports memory only — it does not copy SOUL.md, AGENTS.md, or other workspace identity files. Those must be copied separately during provisioning. See the OpenClaw adapter documentation for details on OpenClaw's workspace structure.


3. How Memory Is Indexed

The SQLite backend implements a custom full-stack search engine with no external dependencies. Search combines two signals:

SignalWeightImplementation
Vector cosine similarity0.7 (default)Embedding BLOBs in memories table
BM25 keyword relevance0.3 (default)FTS5 virtual table memories_fts

Weights are configurable via memory.vector_weight and memory.keyword_weight. They are normalized to sum to 1.0.

The merge function (src/memory/vector.rs) fuses scores from both retrieval signals. If embeddings are unavailable (provider is noop or returns a zero-vector), the system falls back to BM25-only search. If FTS5 can't be created, it falls back to vector-only search. Neither failure is fatal.

Embedding Providers

ZeroClaw supports pluggable embedding through the EmbeddingProvider trait:

ProviderConfig valueNotes
OpenAI"openai"Uses text-embedding-3-small
Custom URL"custom:https://..."Any OpenAI-compatible endpoint
None"none" or "noop"No embeddings; keyword-only search

When embedding_provider = "none" (the default after onboarding), vector search is disabled. This is a common gotcha — users must explicitly configure an embedding provider and API key to get semantic search.

Embedding Cache

The SQLite backend maintains an LRU embedding cache table (embedding_cache) keyed by (provider, model, content_hash) with a default capacity of 10,000 entries. This avoids redundant API calls when the same content is re-embedded.

Chunking (SQLite)

The SQLite backend stores each MemoryEntry as a single row — there is no multi-chunk splitting like OpenClaw's ~400-token chunking strategy. Each entry is embedded as a whole unit. For auto-saved conversation turns, this means each user message is one embedding. For agent-written memories, each memory_store call produces one entry.

This is simpler than OpenClaw's approach but means very long entries may produce lower-quality embeddings (embedding models have diminishing returns on long input).

The markdown backend uses simple substring matching for recall — no embeddings, no FTS5, no vector search. It reads files and filters by content match. This is deliberately minimal for environments where simplicity and human readability are prioritized over search quality.


4. Identity and Configuration

Identity Formats

ZeroClaw supports two identity formats, configured via [identity].format:

OpenClaw format (format = "openclaw", default): reads SOUL.md, IDENTITY.md, and AGENTS.md from the workspace directory. These are loaded into the system prompt at session start. Fully backward-compatible with OpenClaw workspace files.

AIEOS format (format = "aieos"): loads a JSON document following the AI Entity Object Specification (AIEOS v1.1). Supports structured fields for names, psychology (neural matrix, MBTI, moral compass), linguistics (formality level, slang usage), and motivations. Can be loaded from a file (aieos_path = "identity.json") or inline (aieos_inline = '{...}').

Configuration (config.toml)

All ZeroClaw settings live in a single TOML file at ~/.zeroclaw/config.toml. Memory-relevant sections:

[memory]
backend = "sqlite"              # "sqlite", "postgres", "qdrant", "lucid", "markdown", "none"
auto_save = true                # Auto-save user messages
embedding_provider = "none"     # "none", "openai", "custom:URL"
vector_weight = 0.7
keyword_weight = 0.3
# sqlite_open_timeout_secs = 30 # Optional: SQLite lock timeout

# PostgreSQL backend example (when backend = "postgres"):
# [storage.provider.config]
# provider = "postgres"
# db_url = "postgres://user:pass@host:5432/zeroclaw"
# schema = "public"
# table = "memories"
# connect_timeout_secs = 15

[identity]
format = "openclaw"             # "openclaw" or "aieos"
# aieos_path = "identity.json"
# aieos_inline = '{"identity":{"names":{"first":"Nova"}}}'

[secrets]
encrypt = true                  # ChaCha20-Poly1305 AEAD encryption

Secrets Encryption

ZeroClaw encrypts all API keys at rest using ChaCha20-Poly1305 AEAD. The encryption key is stored at ~/.zeroclaw/.secret_key with 0600 permissions. The ALF adapter does not read that keystore — it never scrapes ZeroClaw's API keys into an archive. Secret backup in ALF is the agent's separate, explicit vault (see Credentials below).


5. Mapping ZeroClaw Memory to ALF

Record Boundary Strategy

ZeroClaw's record boundaries are much simpler than OpenClaw's because each MemoryEntry is already a discrete record with a UUID:

BackendSourceBoundaryNotes
SQLitememories table rowsOne row → one MemoryRecordNatural boundary
MarkdownDaily/session .md filesOne file → one MemoryRecord OR split on ## headingsSee below

SQLite backend: Each row in the memories table maps 1:1 to an ALF MemoryRecord. The entry's UUID becomes the record ID (wrapped in UUID format). This is the clean path.

Markdown backend: The adapter applies the same H2-splitting strategy as the OpenClaw adapter — split on ## headings, with each section becoming one MemoryRecord. If no H2 headings exist, the entire file is one record. Session files follow the same rule.

Stable Record ID Generation

SQLite backend: Use the MemoryEntry.id field directly. ZeroClaw generates UUIDs for each entry, so IDs are inherently stable across exports.

Markdown backend: Use the same UUID v5 strategy as the OpenClaw adapter — deterministic IDs derived from (file_path, section_index) using a fixed namespace UUID.

ZEROCLAW_NS = fixed 16-byte namespace UUID
record_id = UUID_v5(ZEROCLAW_NS, "{relative_path}:{section_index}")

Field Mapping: MemoryRecord

ALF FieldZeroClaw Source (SQLite)ZeroClaw Source (Markdown)Notes
idMemoryEntry.id (as UUID)Generated UUID v5SQLite IDs are native
agent_idFrom config or manifestFrom configDerived from workspace path
contentMemoryEntry.contentSection markdownVerbatim
memory_typeClassified by categoryClassified by fileSee table below
source.runtime"zeroclaw""zeroclaw"Constant
source.runtime_versionFrom zeroclaw --versionSameBest-effort
source.origin"sqlite""workspace"Backend name
source.origin_fileNone (database)Workspace-relative pathe.g., "memory/2026-01-15.md"
source.extraction_methodAgentWrittenAgentWrittenAlways AgentWritten (there is no SystemGenerated variant); auto-saved entries instead get an auto_save tag
temporal.created_atcreated_at column (RFC 3339)File mtime or date from filenameThe real column — there is no timestamp column
temporal.updated_atupdated_at columnNoneNewly mapped
source.session_idsession_id columnNoneNewly mapped
status / supersedessuperseded_by columnActive (Archived for archive/)Non-null superseded_bystatus: Superseded + supersedes; the value is preserved verbatim for restore
namespacenamespace column (stored value)Classified by fileUses the stored column, not the category
categorycategory columnFrom file typeReal taxonomy: core/episodic/procedure/conversation/credentials
confidenceimportance column (REAL, default 0.5)NoneNewly mapped
tags[category, "zeroclaw"] (+ auto_save for user_msg_/assistant_resp_ keys)[file_category, "zeroclaw"]
embeddingsExtract BLOB from memories table (NULL when provider = none)NoneBest-effort extraction
raw_source_format{ "key", "category", "created_at", "updated_at", "session_id", "namespace", "importance", "superseded_by" }{ "line_start": N, "line_end": N }Every native column stashed for a lossless ZeroClaw→ZeroClaw restore
agent.extra{ "zeroclaw_agent_id", "zeroclaw_alias" }Slice provenance; restore resolves the target agent by alias
extra.zeroclaw_keyMemoryEntry.keyNonePreserve key for round-trip

Memory Type Classification

The real 0.8.2 category taxonomy is core / episodic / procedure / conversation / credentials (not the older Core/Daily/Custom enum):

categorymemory_typeRationale
coreSemanticDurable facts and preferences
episodicEpisodicDay-to-day observations
procedureProceduralSaved procedures
conversationEpisodicAuto-saved turns (user_msg_/assistant_resp_ keys → auto_save tag)
credentialsSemanticSynced verbatim — ALF is framework-neutral on secrets in memory; the credentials category tag keeps such rows identifiable
any otherSemanticDefault

Namespace Assignment

Sourcenamespace
MemoryCategory::Core"core"
MemoryCategory::Daily"daily"
MemoryCategory::Conversation"conversation"
MemoryCategory::Custom(label)"custom:{label}"
Markdown daily files"daily"
Markdown session files"session"
Markdown archived filesOriginal namespace + status Archived

Embedding Extraction

For the SQLite backend, embeddings are stored as BLOBs in the memories table. The adapter reads these directly and includes them in the ALF embeddings field with metadata:

{
  "model": "openai/text-embedding-3-small",
  "dimensions": 1536,
  "vector": [0.012, -0.034, ...],
  "computed_at": "2026-01-15T10:30:00Z",
  "source": "runtime"
}

The provider is folded into the namespaced model string (e.g. openai/...); the ALF Embedding type has no separate provider field. Vectors are decoded best-effort as packed little-endian f32 (falling back to f64), accepted only when the dimension count is between 64 and 4096.

If embedding_provider = "none", no embeddings are exported. The markdown backend never has embeddings.

Portability caveat: Embeddings are model-specific. They are useful for restoring to the same ZeroClaw installation but may not be meaningful in a different runtime using a different embedding model.

Partition Assignment

Same quarterly partitioning as the OpenClaw adapter:

memory/2026-Q1.jsonl   # Jan–Mar 2026
memory/2026-Q2.jsonl   # Apr–Jun 2026

Records are assigned to partitions based on temporal.created_at.


6. Mapping Other Layers to ALF

Identity (ALF §3.2)

ZeroClaw supports two identity formats, both mapped to ALF:

OpenClaw format (default):

AIEOS format:

ZeroClaw FileALF FieldMapping
SOUL.mdidentity.prose.soulFull content
IDENTITY.mdidentity.prose.identity_profileFull content
AGENTS.mdidentity.prose.operating_instructionsFull content
identity.json (AIEOS)identity.structured.* + identity.raw_sourceStructured extraction + raw JSON
config.toml [identity]identity.source_format"openclaw" or "aieos"

Principals (ALF §3.3)

Credentials (ALF §3.4)

ALF Layer 4 is the agent's explicit, self-managed vault, not a scrape of ZeroClaw's keystore. The agent chooses what to back up and stores each secret with alf vault add; ALF never auto-exports config.toml [secrets], provider keys, or channel tokens. Each record is real AEAD ciphertext (XChaCha20-Poly1305 by default; AES-256-GCM is a registered alternative), encrypted client-side under a per-agent key kept in a local file (~/.zeroclaw/state/<alf-agent-id>/.alf-vault-key, mode 0600). The vault is key-only — there is no passphrase — and the key never leaves the machine: the sync service is zero-knowledge, there is no escrow, and ALF never sees, transmits, or recovers it. Only the ciphertext travels in the archive; an alf import on a machine that holds the same key decrypts it in place.

Separately, ZeroClaw's own credentials memory category (rows tagged credentials in brain.db) is treated as memory, not vault: it is synced verbatim as Semantic records, because ALF is framework-neutral about secrets an agent chose to remember.

Raw Source Preservation

All workspace files are preserved verbatim under raw/zeroclaw/ in the ALF archive:

raw/zeroclaw/
├── config.toml              # Full configuration (secrets redacted)
├── SOUL.md
├── IDENTITY.md
├── AGENTS.md
├── USER.md
├── HEARTBEAT.md
├── TOOLS.md
├── identity.json            # AIEOS identity (if present)
└── memory/                  # Markdown backend files (if present)
    ├── 2026-02-15.md
    └── archive/
        └── 2026-02-08.md

For the SQLite backend, the brain.db file is excluded from raw sources (it can be megabytes and the data is already captured as structured MemoryRecord values). The config.toml has API key values replaced with "<redacted>" before inclusion.


7. Gaps, Risks, and Design Decisions

Addressed

ChallengeResolution
Two backends with different data modelsAdapter detects backend from config.toml and uses backend-specific extraction
SQLite has native UUIDs, Markdown does notSQLite IDs used directly; Markdown uses deterministic UUID v5
Auto-saved entries vs. agent-written entriesAll ZeroClaw entries map to AgentWritten; per-turn auto-saved entries (key prefixes user_msg_ / assistant_resp_) are flagged with an auto_save tag
Archived Markdown filesExported with status = Archived to distinguish from active memory
config.toml contains secretsAPI key values redacted before inclusion in raw sources

Accepted Limitations

LimitationImpactMitigation
lucid backend not supportedUsers of Lucid must export from SQLite fallbackDocument in adapter help text
none backend has no dataNothing to exportAdapter returns empty archive with manifest only
postgres / qdrant backends not supportedInstalls using a remote backend export zero memory records (silently)Adapter extracts only from sqlite and markdown; a remote-backend extractor is future work, and the adapter should warn when it encounters an unsupported backend
Live conversation buffer is ephemeralThe rolling in-memory context window is not persistedPer-turn auto-saved messages (user_msg_ / assistant_resp_) are persisted to the backend and exported (tagged auto_save); only the live buffer is lost
No per-entry timestamps in Markdown backendFile mtime used as fallbackAcceptable — same approach as OpenClaw adapter
Embedding portabilityModel-specific vectorsIncluded best-effort with model metadata; consumer can choose to re-embed
AIEOS identity extensionsFields like psychology.neural_matrix have no ALF equivalentStored in identity.extra as JSON blobs

ALF Type Fitness Assessment

All ZeroClaw concepts map cleanly to existing alf-core types:

ZeroClaw ConceptALF TypeFit
MemoryEntryMemoryRecord✅ Direct (ZeroClaw entries are simpler than ALF records)
MemoryCategorymemory_type + namespace✅ Clean mapping via classification tables
OpenClaw-format identityIdentity (prose)✅ Same as OpenClaw adapter
AIEOS identityIdentity (structured + extra)✅ Core fields map; extensions go to extra
USER.mdPrincipalsDocument✅ Same as OpenClaw adapter
Encrypted secretsCredentialsDocument✅ Agent's explicit AEAD vault (ciphertext)
config.tomlNot memory — goes to raw/✅ Preserved for reference

No changes needed to alf-core. All ZeroClaw concepts fit within the existing type system.


8. References