LadyM

English | 简体中文
A brain-inspired, multi-tier memory framework that lets LLM agents recall workspace
knowledge and codebase analysis with one keyword — instead of re-Read-ing and
re-Grep-ing the same files every turn.
LadyM caches your workspace's understanding — code analysis, decisions, skills, and
episodes — into a hierarchical, consolidating, decaying memory that any agent can recall
through a single keyword. Written in Go as a single static binary with zero cgo:
SQLite via modernc.org/sqlite, an in-process
brute-force cosine vector index, and gotreesitter
(a pure-Go tree-sitter runtime) for code indexing. Exposed via MCP, Claude Code
Skill, Go SDK, and CLI — plus silent hook capture for Claude Code and
Codex (ladym hook) that records agent sessions without the agent spending a single
token. All front-ends call the same engine so behaviour is identical everywhere.
What's New in 0.8.0
- Silent hook capture — Claude Code and Codex lifecycle hooks now feed LadyM
automatically through
ladym hook: session boundaries, user prompts, and tool
calls land as structured L1 episodes via the record_event pipeline
(controlled-vocabulary action classes, resource extraction from tool_input,
deterministic operates_on edges). The hook is a pure observer: stdout stays
empty, unknown events are tolerated, and it never blocks the harness.
ladym hook install — one command merges observer hooks into
~/.claude/settings.json and ~/.codex/hooks.json. User entries and non-hook
keys survive verbatim; installs are idempotent (prefix-matched), a one-time
.ladym-bak backup is taken, and corrupt config files are refused, never
overwritten. Uninstall removes exactly our entries.
- Plugin market —
plugins/ladym (Claude Code plugin: hooks + MCP server)
and plugins/ladym-codex (Codex plugin), catalogued under the ladym-plugins
marketplace in this repository. Plugins carry no binaries: every command points
at the ladym on your PATH, so the binary stays the single source of truth.
- Zero-config db placement — the default db is a per-project
.ladyM/ladym.db (ladym init creates it and appends .ladyM/ to
.gitignore; a legacy flat ./ladym.db keeps being read until init
migrates it). Set LADYM_DB for a unified library, where the workspace
(derived from the event's cwd directory
name) becomes the isolation key. Changing the db never requires reinstalling
hooks.
- Honest scoping note — capture-side sanitization (path exclusion, secret
redaction) is not in 0.8.0; prompts and tool outputs land as captured. See
the known-tradeoffs section of
docs/hooks.md.
What's New in 0.7.0
- Operation-history retrieval (
recall_ops) — agents can ask "how did I last
operate on X?" with a resource × action pivot query: vector search is confined to
resource resolution, an L4 operates_on graph join does the exact filtering, and
ACT-R activation ranks the results. Exposed as the 10th MCP tool, a
ladym recall-ops CLI command, and Go SDK one-shot helpers. The read path stays
LLM-free; unresolvable resources degrade gracefully to plain recall.
- Structured
record_event slots — optional resources and action_class
arguments resolve against a resource registry (L2 type=resource with
lazily-grown aliases) and link operates_on edges deterministically at write
time. Actions normalise against a controlled vocabulary (31 words + aliases,
extensible via [resource] config).
- Self-maintaining L4 graph — deterministic projections on write
(
operates_on, produced_from, calls/imports from code_refs, all
idempotent via ProjectedEdgeID upserts), plus a new MaintainL4 System 2
step: dangling-edge GC every cycle, budgeted slot backfill, gated semantic
linking, and resource alias-merge (LLM-optional, three new l4_* agent ops).
- Re-index hygiene — re-indexing a changed file purges its stale symbol
memories,
code_symbols/code_refs rows and in-process vector entries first
(DeleteCodeArtifactsByFile), so removed symbols no longer linger.
- Docs matched to code — ARCHITECTURE.md claims that ran ahead of the
implementation ("auto-link on write", "callers/callees via L4 expansion") are
now accurate, including an honest scoping note: code-ref projection is
same-file today.
What's New in 0.6.0
- Concurrent HTTP data plane — the global request mutex is gone: requests across
workspaces run fully in parallel via per-request
engine.Scope views, writes
serialize only at the storage backend (SQLite WAL single-writer; Postgres fully
parallel), and the in-memory vector index is now RWMutex-guarded.
- Multi-replica System 2 workers — cycle-level leader election (Postgres advisory
lock / SQLite flock) makes standby workers safe: a crashed replica's cycles are
picked up by any survivor, with no duplicate consolidation spend.
- Prometheus observability — a native
/metrics endpoint (via
prometheus/client_golang) covers request rates, latency histograms, in-flight
requests, and System 2 cycle outcomes; ladym worker --metrics-addr exposes the
same from the worker role.
- Python & TypeScript SDKs — zero-runtime-dependency clients for the HTTP data
plane (
sdk/python, sdk/typescript), feature-parity with the Go SDK, each with
unit suites and a live round-trip integration leg. Test coverage is now 97.9%
(Postgres mode) with scenario-named suites across every package.
The problem
Today's coding agents have no long-term memory. Every turn they re-Read the same files
and re-Grep for the same symbols, burning the context window on rediscovery. Plain vector
RAG helps, but it forgets why a decision was made, how a task was done before, and
what the codebase actually looks like beyond flat chunks.
LadyM turns that throwaway rediscovery into structured, evolving memory.
Why LadyM
|
What you get |
| 🧠 Code memory + general memory in one layer |
L2 fuses tree-sitter code symbols and plain facts in a single store, scored by one activation function. Memory and codebase RAG are one system, not two — a unique spot no other framework occupies. |
| 🔒 Local-first, zero-config start |
Default HashingEmbedding + llm.provider="none" runs the moment install finishes: no network, no model download, no API key. One SQLite file holds everything. |
| 🧬 Brain-inspired six layers + dual-path |
L0–L4 core store (working / episodic / semantic / procedural / associative) plus a System 2 worker that extracts L5 mental models and L6 forward intents, with a supersedes evolution chain so memories evolve instead of piling up. |
| 🔌 Four fronts, one engine |
MCP server, Claude Code Skill, Go SDK, and CLI all call the same Engine — identical behaviour whether you're in Claude Code, Cursor, a script, or the terminal. |
Quick start (30 seconds)
# 1. install (single static binary — no network or API key needed at runtime)
go install github.com/ProjAnvil/LadyM/cmd/ladym@latest
# 2. create the project-local memory db (.ladyM/ladym.db) and update .gitignore
ladym init
# 3. index the codebase you keep re-grepping
ladym index ./src
# 4. ask a question — get the analysis back, no Read/Grep needed
ladym recall "how does password verification work" --code
# 5. store a fact for later
ladym remember "auth uses JWT with 24h expiry" --tags auth,security
That's it — ladym is on your PATH and works fully offline.
How it compares
|
Local-first |
Codebase RAG fused |
Brain-inspired tiers |
Open source |
MCP/Skill/SDK/CLI |
| LadyM |
✅ |
✅ L2 fused |
✅ L0–L6 + System 1/2 |
✅ MIT |
✅ all four |
| mem0 |
partial |
❌ |
flat + graph |
✅ |
SDK |
| Zep / Graphiti |
cloud |
❌ |
temporal knowledge graph |
✅ |
SDK |
| Letta (MemGPT) |
self-host |
❌ |
OS-style agent runtime |
✅ |
SDK |
| Tencent Hy-Memory |
cloud SaaS |
❌ |
✅ L1–L6 |
❌ |
SDK |
LadyM borrows academic ideas from all of them (see
ARCHITECTURE.md §10) — its distinctive bet is fusing codebase RAG into
a brain-inspired memory that runs fully local.
Architecture
System 1 — online (ms) System 2 — offline worker (s–min)
┌───────────────────────────┐ ┌───────────────────────────┐
│ L0 Working (scratch)│ │ L5 Mental Model │
│ L1 Episodic (events) │ extract │ (cognitive frameworks)│
│ L2 Semantic (facts+CODE)│─────────▶│ L6 Forward Intent │
│ L3 Procedural (playbook)│ │ (predictive actions) │
│ L4 Associative (graph) │ └───────────────────────────┘
└───────────────────────────┘
▲
read ◀──── recall(query) : two-tier (lightweight → deep) + reflection gate
write ───▶ encode / consolidate / proceduralize / link / forget / decay
| Layer |
Brain analogue |
LadyM behaviour |
| L0 Working |
Prefrontal cortex (working memory) |
In-process bounded scratch buffer |
| L1 Episodic |
Hippocampus |
Time-stamped events; base-level decay |
| L2 Semantic |
Neocortex |
Consolidated facts and code analysis — one store |
| L3 Procedural |
Basal ganglia |
Playbooks + verified snippets, versioned |
| L4 Associative |
Associative cortex |
Zettelkasten edges with temporal validity |
| L5 Mental Model |
(metacognition) |
Cognitive frameworks abstracted from recurring episodes |
| L6 Forward Intent |
(planning) |
Predictive next-actions, asynchronously distilled |
Operations: encode (perception), consolidate (hippocampal replay → neocortex),
proceduralize (skill acquisition), recall (retrieval), link (association),
forget (synaptic pruning), reflect (metacognition).
The read path is heuristic-only — no LLM judge is ever invoked during recall, so it
stays fast and predictable. For the full cognitive-science provenance (what's borrowed
from MemGPT, mem0, A-MEM, Zep, HyMem, CoALA, ACT-R, and Anthropic's context-engineering
work) see ARCHITECTURE.md.
Install
As a global CLI (recommended)
go install github.com/ProjAnvil/LadyM/cmd/ladym@latest
This drops a single static ladym binary on your Go bin path
($(go env GOPATH)/bin). Requires Go 1.26+ to build; the binary itself has no
runtime dependencies.
Build from a clone
git clone https://github.com/ProjAnvil/LadyM.git && cd LadyM
go build -o bin/ladym ./cmd/ladym
./bin/ladym stats
Or use the install script, which builds and copies the binary to ~/.local/bin:
scripts/install.sh # or: scripts/install.sh /custom/bin/dir
For development
git clone https://github.com/ProjAnvil/LadyM.git && cd LadyM
go build ./... # compile everything
go test ./... # full suite, fully offline
The module is pure Go (github.com/ProjAnvil/LadyM) with a handful of library
dependencies — no native toolchain needed on macOS/Linux/Windows.
Integration
All four fronts call the same Engine, so behaviour is identical everywhere.
MCP server (Claude Code, Cursor, …)
Add to your MCP client config:
{
"mcpServers": {
"ladym": {
"command": "ladym",
"args": ["serve", "--db", "/absolute/path/to/ladym.db"]
}
}
}
The server speaks MCP over stdio and exposes ten tools — recall, recall_ops,
remember, record_event, search_code, index_code, consolidate, stats, link,
forget — described in mcp/server.go. record_event logs an L1
episodic event that feeds the System 2 worker's consolidation (L1 → L2) and the
gated L5 / L6 extractors (record ~3+ to arm those cycles). It also accepts optional
structured slots — resources (names/aliases) and action_class — which resolve
against a resource registry (L2 type=resource with lazily-grown aliases) and link
operates_on edges deterministically at write time.
recall_ops answers operation-history pivot queries — "how did I last operate on X" —
via resource resolution (vector) → L4 operates_on join (exact) → ACT-R ranking.
Read path is LLM-free; unresolvable resources degrade to plain recall:
{ "tool": "recall_ops",
"arguments": { "resource": "ladym.db", "action": "migrate", "outcome": "fail", "limit": 5 } }
Same query from the CLI:
ladym recall-ops --resource "ladym.db" --action migrate --outcome fail
Actions normalise against a controlled vocabulary (31 words + aliases, exact match;
extend via the [resource] config section). The [maintain] section tunes the System 2
worker's L4 maintenance: dangling-edge GC runs every cycle; slot backfill, gated
semantic linking and resource alias-merge (l4_backfill / l4_link / l4_alias_merge
ops, LLM-optional) are budgeted and disabled together with [maintain] enabled=false.
Claude Code Skill
A drop-in skill is in skills/ladym-recall.md. Copy it to your
.claude/skills/ directory and the agent will pull workspace memory into context with a
keyword instead of re-reading files.
Go SDK
The ladym package is a one-import facade over the engine
(ladym/ladym.go):
import "github.com/ProjAnvil/LadyM/ladym"
eng, err := ladym.NewEngine(ladym.DefaultConfig())
if err != nil {
// handle error
}
defer eng.Close()
// index once (incremental — skips unchanged files)
report, err := eng.IndexCode("./src", false, "", nil)
// write path
eng.Remember("auth uses JWT with 24h expiry", ladym.LayerSemantic, ladym.TypeFact,
[]string{"auth"}, nil, "sdk", "")
eng.RecordEvent("claude", "fixed login bug", "", "success", nil, nil)
// read path: two-tier retrieval, ACT-R activation ranking
resp, err := eng.Recall("how does auth work", "", 8, nil, nil, 0)
for _, r := range resp.Results {
fmt.Printf("%.3f [%s] %s\n", r.Score, r.Memory.Layer, r.Memory.Summary)
}
// code-only shortcut
codeResp, err := eng.SearchCode("verify password", 8, "")
// cognitive operations
eng.Consolidate("", 0) // L1 episodes → L2 facts (ADD/UPDATE/DELETE/NOOP)
eng.Proceduralize("", 0) // recurring successful episodes → L3 playbooks
eng.Decay("", true, 0, 0) // ACT-R base-level forgetting (dry run)
eng.Link(srcID, dstID, "depends_on") // Zettelkasten edge
One-shot helpers — ladym.Recall(query, dbPath, workspace, topK),
ladym.Remember(...), ladym.IndexCode(...) — open a short-lived engine, run, and
close, for scripts that don't want to manage the lifecycle.
Python SDK (MCP wrapper)
Python apps talk to the Go engine through wrapper/py — a thin typed
client that spawns the ladym serve MCP server (JSON-RPC 2.0 over stdio) and exposes
the nine tools (recall, remember, record_event, search_code, index_code,
consolidate, stats, link, forget) as Python methods. No memory logic lives in
Python; the Go binary is the single source of truth.
from ladym_wrapper import LadymClient # sync
from ladym_wrapper import AsyncLadymClient # async
with LadymClient() as client:
client.remember("deploys go through Argo CD", source="notes")
hits = client.recall("how do we deploy?")
The Go binary is resolved in order: binary= argument → LADYM_BIN env var → PATH →
repo-local bin/ladym. Requires Python ≥ 3.12. See
wrapper/py/README.md for details. (The full 0.2.x Python
implementation is preserved on the
python branch.)
Python SDK (HTTP data-plane)
For talking to a running ladym serve --http deployment (including multi-replica
enterprise setups behind the gateway) there is sdk/python — package
ladym-client, zero runtime dependencies, Python ≥ 3.10. It mirrors the Go HTTP
client (client/golang): remember / recall / record_event / consolidate /
stats / link / forget plus the memories CRUD, with Basic-auth support.
from ladym_client import Client
c = Client("http://127.0.0.1:8080", username="alice", password="s3cret")
c.remember("deploys go through Argo CD", tags=["ops"])
for hit in c.recall("how do we deploy?").results:
print(hit.score, hit.memory.summary)
See sdk/python/README.md for install and testing.
TypeScript SDK (HTTP data-plane)
sdk/typescript is the same HTTP client for Node ≥ 20 /
browsers — package ladym-client, zero runtime dependencies (global fetch,
injectable for tests), ESM + .d.ts. The method surface mirrors the Go and
Python SDKs in camelCase (remember / recall / recordEvent / stats /
listMemories / …), errors throw LadymError with the HTTP status.
import { LadymClient } from "ladym-client";
const c = new LadymClient("http://127.0.0.1:8080", { username: "alice", password: "s3cret" });
await c.remember("deploys go through Argo CD", { tags: ["ops"] });
for (const hit of (await c.recall("how do we deploy?")).results) {
console.log(hit.score, hit.memory.summary);
}
See sdk/typescript/README.md for install and testing.
Injecting your own langchain-golang models
If your app already configures langchain-golang chat / embedding models
(with api key, base URL, model), wrap them and pass them straight to the Engine via
ModelRouting — no need to re-declare credentials in LadyM's config
(adapter/adapter.go):
import (
"github.com/ProjAnvil/LadyM/adapter"
"github.com/ProjAnvil/LadyM/ladym"
"github.com/projanvil/langchain-golang/core/modelconfig"
"github.com/projanvil/langchain-golang/partners/openai"
)
eng, err := ladym.NewEngineWithModels(ladym.DefaultConfig(), &adapter.ModelRouting{
Consolidate: adapter.WrapChatModel(openai.NewChatModel(
modelconfig.WithModel("gpt-4o"),
modelconfig.WithAPIKey(key),
modelconfig.WithBaseURL(url),
), ""),
AttentionGate: adapter.WrapChatModel(openai.NewChatModel(
modelconfig.WithModel("gpt-4o-mini"),
modelconfig.WithAPIKey(key),
), ""),
Embedding: adapter.WrapEmbeddings(openai.NewEmbeddings(
modelconfig.WithModel("text-embedding-3-small"),
modelconfig.WithAPIKey(key),
)),
})
Each of the five cognitive ops (consolidate, proceduralize,
attention_gate, l5_mental_model, l6_forward_intent) can take a
different model; unset ops fall back to Config.
LangGraph
The langgraph/ package integrates LadyM as a long-term memory layer for
langchain-golang LangGraph / LangChain agents. Two equivalent paths:
- Tools —
langgraph.CreateTools(eng, workspace, defaultTopK) returns LangChain
tools (recall_memory, remember_fact, search_code) for ReAct-style agents where
the LLM decides when to recall/remember.
- Nodes —
langgraph.CreateRecallNode(eng, topK, prefix, wsFn) /
langgraph.CreateRetainNode(eng, wsFn) return graph NodeFuncs that inject recalled
memory as a SystemMessage every turn and store the latest turn automatically.
langgraph.WorkspaceFromUserID() gives per-user workspace isolation from the
run-scoped user_id.
See docs/langgraph-integration.md for the design
walkthrough.
Hermes Agent (memory provider)
LadyM ships a Hermes Agent memory
provider in integrations/hermes/ — Hermes recalls through
LadyM's two-tier recall before each turn, records every turn as an L1 episodic event,
and consolidates (L1 → L2) at session end and before context compression. The plugin
is a zero-dependency Python adapter that drives the ladym binary over MCP stdio;
the Go engine remains the single source of truth.
# 1. install the plugin (subdirectory install — no manual file copying)
hermes plugins install ProjAnvil/ladyM/integrations/hermes
# 2. activate the provider, then bootstrap the ladym binary
# (the `hermes ladym` CLI only exists once ladym is the active provider;
# add --fulldict for the embedded CJK dictionary)
hermes memory setup ladym
hermes ladym install
# 3. verify
hermes memory status # Provider: ladym, available ✓
hermes ladym status # binary path, effective config, store stats
The database lives at $HERMES_HOME/ladym/ladym.db (per-profile isolation) and
defaults to the offline hashing embedder — no API key, no external service. A pip
install (git+https://github.com/ProjAnvil/ladyM.git#subdirectory=integrations/hermes,
entry-point discovery) works too; see
integrations/hermes/README.md.
CLI
ladym index ./src # index a codebase (incremental)
ladym recall "auth flow" # recall across code AND facts
ladym recall "auth flow" --code --json # code-only, machine-readable
ladym remember "..." --tags auth # store a fact
ladym record --agent claude --action "fixed login bug" --outcome success
ladym consolidate # L1 → L2
ladym worker --once # fire the System 2 L5/L6 extractors
ladym stats # what's in memory
ladym doctor # report config / storage problems
ladym doctor is read-only: no LLM calls, no writes, no network. It prints one
line per check (home dir, legacy ~/.ladym directory, global config, database,
legacy flat db, .gitignore, dictionary dir) and exits non-zero only when a
finding is an error.
Run ladym <command> --help for flags; ladym completion <shell> generates shell
autocompletion. Each indexed result carries the symbol's identity, signature,
docstring, body snippet, and source file — plus the callers/callees are queryable
via the symbol graph. That is what saves the agent from re-reading.
Configuration
Config resolves through (highest precedence first): CLI flags → LADYM_* env vars →
./ladym.toml → ~/.ladyM/config.toml → built-in defaults. ladym --config <path>
loads an extra TOML file on top.
| Env var |
Default |
Purpose |
LADYM_DB |
./ladym.db |
SQLite path (one DB per project by default) |
LADYM_WORKSPACE |
default |
Multi-workspace isolation in a shared DB |
LADYM_EMBEDDING |
hashing |
hashing / openai / ollama / http |
LADYM_DICT_DIR |
~/.ladyM/dict |
CJK dictionary dir; point at a shared volume in microservice deployments |
LADYM_EMBEDDING_MODEL |
(provider default) |
Model name for a hosted embedding provider |
LADYM_EMBEDDING_BASE_URL |
(provider default) |
Override embedding API base URL (OpenAI/Ollama-compatible) |
LADYM_EMBEDDING_API_KEY_ENV |
(none) |
Name of the env var holding the embedding API key |
LADYM_LLM_PROVIDER |
none |
none / openai / anthropic / ollama / http |
LADYM_LLM_BASE_URL |
(provider default) |
Override LLM API base URL (OpenAI/Ollama-compatible) |
LADYM_LLM_MODEL |
gpt-4o-mini |
LLM model name for the consolidation classifier |
LADYM_LLM_API_KEY_ENV |
(none) |
Name of the env var holding the LLM API key |
LADYM_LLM_MAX_TOKENS |
1024 |
Global LLM token limit |
LADYM_LLM_TEMPERATURE |
0.2 |
Global LLM temperature |
LADYM_LLM_TIMEOUT_S |
30 |
Global LLM timeout in seconds |
LADYM_ENABLE_WAL |
true |
Enable SQLite WAL journal mode (default on so multiple processes can share one db; set false on filesystems without WAL support) |
base_url support lets you point embedding and LLM calls at any OpenAI/Ollama-compatible
endpoint (e.g. vLLM, LiteLLM, local Ollama); the http providers let you template an
arbitrary embedding/LLM HTTP API. Finer knobs (LADYM_EMBEDDING_TIMEOUT_S,
per-op overrides under [agents], and
the recall/activation weights) live in TOML or on the config.Config struct — see
config/config.go. Secret literals in TOML are rejected with a
warning; use <name>_env indirection or the secret store below.
LLM numeric settings (max_tokens, temperature, timeout_s) in [llm],
their flat llm_* aliases, and [agents.<op>] overrides are strictly
validated per source, before merging. An invalid value is an error even when
a higher-priority source would override it, the operation is unused, or its
provider is none. Within one source, specifying both flat and nested forms of
the same global numeric setting is an error; across sources, normal precedence
still applies.
max_tokens accepts integers, integral floats, and complete decimal integer
strings such as " 2048 "; fractional values, overflow, and trailing text such
as "2048junk" are rejected. Floating settings accept finite numbers and
complete numeric strings, including scientific notation; NaN and infinity are
rejected. Missing settings inherit defaults. Empty environment variables count
as unset, but an explicitly empty string in TOML or programmatic overrides is
invalid. Zero and negative values retain their existing behavior: this is
conversion validation, not provider-specific parameter-range validation.
Errors identify the source and field without dumping configuration values.
Secret store (encrypted keys at rest)
Provider API keys can be stored encrypted (AES-256-GCM) under ~/.ladyM/ instead of
being pasted into your shell rc or CI secrets:
ladym config set-master-key # first time: generate a random master key
ladym config set DEEPSEEK_API_KEY sk-... # store a key (encrypted)
ladym config list # list names (values never echoed)
ladym config rm DEEPSEEK_API_KEY # remove
ladym config reset-master-key <newpass> # rotate master key; all secrets re-encrypted in place
Key resolution order — LLM providers: Config.LLMAPIKey plaintext field (dev escape
hatch, only honoured with allow_plaintext_secrets, off by default) → secret store
(~/.ladyM/secrets.enc) → process env var named by api_key_env. Embedding
providers skip the plaintext tier, resolving: secret store → env var. If none is set,
commands fail fast with a one-line ConfigError naming the env var and the fix command
(exit 1; MCP tools return a structured error instead of a stack trace).
Security boundary: the store guarantees encryption at rest — it prevents plaintext
leaking via cat secrets.enc, shoulder-surfing, or accidental paste into chat/logs/commits.
It does not protect against full ~/.ladyM/ exfiltration: the master key and ciphertext
live in the same directory (the trade-off for non-interactive MCP / background workers that
must decrypt without a passphrase prompt). For stronger isolation, keep ~/.ladyM/ on
encrypted storage and rely on OS file permissions (dir 0700, files 0600). Losing
master.key makes all secrets unrecoverable — back it up.
More CLI surface:
| Command |
What it does |
ladym serve --http :8080 |
HTTP data-plane API (/api/*, optional Basic auth) plus the embedded management console at / (login, memory CRUD, user admin, stats); Prometheus exposition at /metrics (auth-exempt, next to /healthz) |
ladym config <sub> |
Encrypted secret store: set / set-master-key / reset-master-key / list / rm |
ladym worker |
Background System 2 consolidation daemon; flags: --once, --interval N (seconds), --metrics-addr :9090 (serve /metrics + /healthz) |
Testing
go test ./... # full suite, fully offline
go test ./engine/ -v # one package
go vet ./... # lint
The whole suite runs without network and without model downloads — the default
HashingEmbedding is deterministic and fully offline, so CI is hermetic.
CJK support (Chinese / Japanese / Korean)
LadyM tokenizes Chinese, Japanese, and Korean out of the box: per-character
tokenization plus adjacent bigrams, fully offline, zero configuration — CJK queries
never return an empty set. Dictionary-backed word segmentation (better recall
quality) is opt-in, and LadyM never downloads anything on its own: every download
is user-triggered from the console or the admin API.
Enabling a dictionary
Four downloadable variants, all sha256-pinned to gse v1.0.2 (jsDelivr mirror first,
GitHub raw fallback):
| Variant |
Content |
Download |
zh (default) |
Chinese, simplified + traditional |
8.2 MB |
zh_s |
Chinese, simplified only |
4.9 MB |
zh_t |
Chinese, traditional only |
3.4 MB |
jp |
Japanese, kanji + kana (kana segments by word too) |
22.6 MB |
- Console (both editions): Settings → Memory → pick a variant → 下载词典.
Verified download, hot reload — no restart, no shared state.
- API (admin-gated):
POST /api/cjk_dict/download with {"dict": "zh"}; an
optional "mirror_base" points at an internal mirror for air-gapped clusters.
GET /api/cjk_dict reports status + the variant enumeration; DELETE /api/cjk_dict
removes the dictionary.
- Docker: dict-baked images — see below.
The dictionary directory defaults to ~/.ladyM/dict and is configurable three ways
(priority high → low): the --dict-dir flag on serve / worker / ladymconsole,
the LADYM_DICT_DIR env var, or dict_dir in ladym.toml.
Dict-baked images (recommended for Docker deployments)
One command builds and runs every role with the dictionary baked into the image —
no shared volume, no downloads, no internet at runtime:
# dev group (pg + api + worker)
docker compose -f docker-compose.dev.yml -f docker-compose.dev.dict.yml up -d --build
# enterprise three-tier (gateway + api×2 + console + worker + pg)
docker compose -f docker-compose.enterprise.yml -f docker-compose.enterprise.dict.yml up -d --build
How it works, and what you can tune:
- The main
Dockerfile has a dict target: a thin data layer (dictionary files at
/opt/ladym/dict, LADYM_DICT_DIR set in the image) on top of the regular image.
A plain docker build . stays dictionary-less — the dict layer only builds on demand.
- Pick the variant in the compose override's
x-dict-build anchor (DICT_VARIANT: zh | zh_s | zh_t | jp).
- Air-gapped builds: place the pinned files under the repo's
dict/ directory and
the image builds with zero network.
- Already have a released image? Stack the layer without rebuilding:
docker build -f Dockerfile.dict --build-arg BASE=ladym-enterprise:v0.5.0 -t ladym-enterprise:v0.5.0-dict .
- Dictionary refresh = rebuild the layer (base layers stay cached, seconds) + restart;
LadyM version upgrades don't touch the dict layer.
Microservices without baked images
Point every instance's dictionary directory at a shared volume
(LADYM_DICT_DIR=/data/cjk-dict — wired in the reference docker-compose.enterprise.yml).
One download on any instance provisions all of them: instances re-probe the shared
manifest and pick up new dictionaries or variant switches within ~30s, no restarts.
K8s equivalent (PVC + env + volumeMount) and the full comparison of multi-machine
patterns live in docs/deployment.md.
Behavior guarantees
- No auto-download — the tokenization path never touches the network (a contract
test enforces it).
- Graceful degradation — without a dictionary, CJK still tokenizes per character;
similarity and recall keep working.
- Integrity — every file is sha256-verified before anything is written; failed or
tampered downloads leave the existing dictionary untouched.
- Offline binary variant —
go build -tags fulldict (or the side-effect import
below) compiles the zh dictionary into the binary (~+31MB) for non-container offline
distribution.
Downstream Go consumers & release assets
Library consumers (go.mod) get dictionary-backed tokenization three ways — the module
version never changes:
- Runtime download (nothing to build differently): trigger via the console /
admin API as above; hosts may also call
storage.DownloadCJKDict() explicitly.
- Side-effect import (no build-script change):
import _ "github.com/ProjAnvil/LadyM/storage/fulldict" links the embedded
dictionary in (~+31MB) — the import edge is the switch, so uninterested consumers
stay small.
- Build flag:
-tags fulldict — same dictionary, chosen in the build command;
this is what the prebuilt ladym-personal-fulldict-* release assets use.
There is deliberately no v0.5.0-fulldict module version: semver reads that suffix as
a pre-release of v0.5.0 and it would confuse go get.
Binary users: every v* GitHub release carries per-platform tarballs —
ladym-personal-* (default), ladym-personal-fulldict-* (embedded dictionary),
ladym-enterprise-* — built by .github/workflows/release.yml on tag push, with a
SHA256SUMS file. Local equivalents: make package-personal / package-fulldict /
package-enterprise, or LADYM_BUILD_TAGS=fulldict scripts/install.sh from a clone.
Documentation
- ARCHITECTURE.md — full design: the six memory layers, cognitive
operations, two-tier retrieval, the ACT-R activation function, storage layout, and the
code-indexing subsystem. English only.
- scenarios/ — executable end-to-end scenarios (S01 write/recall, S03
code index, S07 L5 mental model, S08 L6 forward intent, S09 attention gate, …) that
double as a living spec. scenarios/README.md is in Chinese.
- docs/ — deep dives (code-indexing analysis, LangGraph integration) and
the enterprise deployment guide (container image, docker-compose
reference deployment, workspace isolation, ops baseline).
- The Go test suite (
*_test.go beside each package) — the executable specification.
Status & roadmap
✅ Six-layer engine, two-tier recall, ADD/UPDATE/DELETE/NOOP consolidation,
proceduralization, decay, pure-Go tree-sitter indexer (full symbol specs for
Python/JS/TS/Go/Rust/Java/C/C++, line-window chunking for Kotlin/C#/Ruby/PHP/Swift/Scala/
Bash/Lua/SQL/HTML/CSS), MCP server, CLI, Skill, Go SDK, pluggable providers + TOML
config, System 2 background worker, L5 mental-model / L6 forward-intent extraction,
embedded management console (ladym serve --http, Vue 3 SPA under console/),
encrypted secret store.
🚧 Next: GraphRAG-style cross-file ref resolution and multi-modal episodes.
Contributing
Contributions are welcome. The fastest way to help:
- Run
go test ./... (hermetic) and go vet ./... before submitting.
- Add a scenario under scenarios/ or a
*_test.go beside the changed
package for any new behaviour — they are the spec.
- Keep the read path heuristic-only; route any LLM use through the System 2 worker.
- Follow the Go version in
go.mod: prefer standard helpers such as slices.Clone,
slices.Contains, slices.SortStableFunc, and strings.SplitSeq over equivalent
hand-written loops. Preserve ordering, nil/empty semantics, and public output.
Inspect modernization suggestions with go fix -diff ./...; for the evaluation
harness, also use go fix -diff -tags eval ./eval. The -diff flag leaves files
unchanged so each suggestion can be checked for behavioral compatibility.
For the design rationale behind any layer or operation, read
ARCHITECTURE.md first, then open an issue to discuss before large
changes.
Citation
If LadyM informs your research or project, a citation is appreciated:
@misc{ladym2026,
title = {LadyM: A brain-inspired, multi-tier memory framework for LLM agents and codebase RAG},
author = {ProjAnvil},
year = {2026},
url = {https://github.com/ProjAnvil/LadyM}
}
License
MIT © ProjAnvil