| name | semantic-memory |
| description | Meaning search over three separate axes — own pi/Claude sessions, the public garden, and OpenClaw bot memory — always naming which axis a hit came from. Use for past decisions, concept discovery, cross-lingual retrieval, and time/project session slices. Start with 5, choose, then open. Exact title/person → denotecli; whole-day truth → timeline/day-query. Triggers: semantic memory, session search, knowledge search, 의미 검색, 과거 결정. |
semantic-memory
Search, choose, then open. CLI: {baseDir}/semantic-memory;
the table omits this prefix. The CLI surface is exactly
search-sessions | search-md | search-openclaw | search-knowledge | status | reindex — anything else named here is a sibling skill, invoked on its own,
not a subcommand.
Calling one as a subcommand returns {"error":"Unknown command"}
(reproduced at the bot 2026-09-03, reported by the GPT bot on andenken#10).
API
| Intent | Call | Next move |
|---|
| Past decision/conversation | search-sessions "query" --limit 5 | Inspect top 3–5; choose one session. |
| Known time/project | search-sessions "query" --project andenken --date-from ISO --date-to ISO --mode recent | Caller supplies a half-open ISO window; no embed/BM25/dictcli. |
| Meaning in known slice | search-sessions "query" --project andenken --date-from ISO --date-to ISO --mode hybrid --limit 5 | Structured filters first, semantic rank second. |
| Public-garden concept | search-md "query" --limit 5 | Choose a document and open its path; --full widens snippets. |
| What a bot said/remembers | search-openclaw "query" --limit 5 · --full widens snippets · from the repo: ./run.sh search:openclaw "query" | Name the axis and the hit's agent when you quote it. No dictcli expansion on this axis by design (andenken#12 open). ⚠️ Off thinkpad this returns state:"absent" with exit 4 — see § Absent axis. |
| Exact title/tag/person | denotecli search "name" --max 5 | Semantic neighbors never prove exact existence. |
| Chosen session context | search-sessions "query" --with-excerpt --excerpt-limit 1 | Surrounding turns; raise to at most 3. Whole session: session-recap --session-file <file> — the file is a corpus path and joins as-is. |
| Health / maintenance | status (CLI) · then the memory-sync / andenken-embed skills | Check freshness; full maintenance is human-gated. |
Three axes — say which one you searched
GLG's rule (2026-09-03): when a hit is quoted, the reader must be able to tell
OpenClaw bot memory from our own sessions from the garden. The three
are different corpora with different provenance, and no one of them is a fallback
for another.
| Axis | Track | What it is | Provenance to carry |
|---|
| sessions | search-sessions | pi / Claude Code transcripts of GLG's own work, every device | harness (pi/claude), device segment of file, project, date |
| garden | search-md | published notes (notes/content) | Denote ID / path |
| openclaw | search-openclaw | chunks the OpenClaw bots embedded themselves (memory + sessions sources, 6 agents), harvested as-is with zero re-embedding | agent (glg/bbot/gpt/…), source (memory/sessions), path, updated_at |
Rules that follow:
- One axis per call, named in the answer. "found in glg-bot's memory" and
"found in your 2026-08 Claude session" are different claims. Never write
"found in memory" for either.
- Scores never cross axes (rule 6 below), and neither do results: no
automatic openclaw fallback into a session search, no session fallback into
openclaw. The existing labeled MD fallback on thin session hits is the only
cross-axis row and it stays labeled.
- OpenClaw
source=memory is the bot's durable memory, not a transcript.
Quote it as what the bot keeps, source=sessions as what was said.
- Chunking differs (OpenClaw
chunkTokens:400), so rule 8's "count documents"
applies per axis with a different density.
- The openclaw track is local only — there is no push step — and never mixes
with the garden (md) axis by any path. The harvest pulls to the authority
(thinkpad), imports, and stops:
openclaw.lance exists on the authority
host, thinkpad, and nowhere else (andenken INVARIANT.md §7.2, "The OpenClaw harvest travels the
other way"; the only rsync in scripts/export-openclaw.sh pulls the export
from the bot host, and scripts/sync-sessions.sh's publish moves
sessions.lance and the manifest only — read 2026-09-03). That is today's
fact, not a rule: if a replica ever needs this axis, the push has to be added
deliberately. Until then, never describe this track as replicated — a
sibling on oracle who reads "replica" will believe it has an index it does
not have. Nor host-relatively: this file is symlinked identically onto
every host, including into the OpenClaw container on oracle, so a phrase like
"on this machine" resolves to whoever is reading. Name thinkpad. md is the exported, public axis. The bot index holds GLG's whole
world — family, health, money, code, in one place (measured 2026-09-03 by the
andenken steward on a sample). GLG's ruling, same day:
"가족은 하나야. 그러려고 합친 거야" — that is the point of harvesting it, not a
problem to filter. The only wall is local versus public; inside local, do not
invent a personal/coding split the owner did not ask for.
- "Not found in sessions, so try bot memory" is not a valid move. Choose
the axis from the question, never from a miss on another axis. This holds
for the pi tools too, where an agent picks the tool by judgment.
This table is the CLI surface. The pi extension exposes a different surface:
its tools are session_search and knowledge_search only (andenken index.ts,
read by the andenken steward 2026-09-03), and there is no openclaw tool in pi
yet — a sibling inside pi cannot reach that axis until andenken adds
openclaw_search (open item on andenken#13). Do not assume the table above is
callable from pi.
The search-openclaw subcommand and the openclaw.lance track shipped in
andenken on 2026-09-03 (andenken#13, "착수 완료" comment): 4,651 chunks imported of
4,683 exported, across 6 agents, with 0 embedding API calls because the vectors
came already computed. Verified callable here the same day —
./run.sh search:openclaw "pi-shell-acp lockSync" --limit 2 returned
{"axis":"openclaw", …, "results":[{"agent":"gpt","source":"memory","path":"MEMORY.md","updated_at":…}]},
so the four provenance fields and the axis label are in the response, not just
in this contract.
Absent axis — a state, not a failure
Every axis has one authority host that builds it; other hosts are consumers
that receive it, or do not have it at all. An axis this host has no copy of is
absent, and absent is an answer, not an error:
{"axis":"openclaw","state":"absent","host":"oracle","authority":"thinkpad",
"path":"…/data/openclaw.lance","reason":"…","next":"…"}
Exit code 4 — neither success (0) nor refusal (1), so it can never be
confused with {"count":0}. Read it as "this host has no copy", never as
"the bots never said that", and never as a permission problem to widen a mount
for (sorge#1 boundary; oracle's ~/repos/gh bind has been read-only since
2026-08-12, nixos-config ORACLE.md).
andenken owns this, for all four axes — sessions, md, org, openclaw
(store.ts describeAxisAbsence / EXIT_AXIS_ABSENT, reached from cli.ts
openAxisForRead; andenken 1e61698). It fires before anything is spent, an
embedding call included, and it refuses on two shapes: the path is missing, and
the path exists but holds no table — the residue a create-on-read leaves
behind. So this skill's wrapper does not gate; calling cli.ts or andenken's
own ./run.sh search:openclaw directly gets the same answer.
Two host-relative remedies come out of one state, which is why state stays a
single value: compare host to authority yourself. Missing on the authority
means not built yet; missing anywhere else means ask the authority. The
next field already says which. (state is reserved for genuinely different
states — stale is the one coming, sorge#1 완료조건 4.)
Env: ANDENKEN_DATA relocates the data dir; ANDENKEN_INDEX_AUTHORITY renames
the authority host, with ANDENKEN_OPENCLAW_AUTHORITY overriding it for that
one axis.
History, because the failure shape is worth keeping: before andenken's gate, a
read call wrote. On a read-only host it died loudly — oracle's
Unable to created lance dataset … (os error 30), which is what opened
sorge#1. On a writable host it silently created an empty index and
answered {"count":0} with exit 0 — an absent axis indistinguishable from an
empty one. The loud EROFS was the lucky case. This wrapper carried its own
openclaw-only gate for one day (ad347ef, retired here); it never checked for
the empty-table residue, so it was the weaker of the two copies — and one
contract implemented twice is the drift this whole issue is about.
Nine operating rules
- Pick the axis. Sessions recover what was said/decided and carry
time/project signals. MD recovers durable public interpretation; it has no
production time/project query axis. OpenClaw recovers what the bots said and
kept, keyed by agent.
- Freshness first. Invoke the
memory-sync skill (not
semantic-memory memory-sync — that is not a subcommand) before recent-work
retrieval when the transcript may have grown. A stale absence is not a
ranking miss.
- Start at 5. MD keeps the same 40-candidate pool for limits up to 10, so 5
lowers reading cost without shrinking findability. Widen only after reading
the first screen and refining concrete names or terms.
- Open, do not re-query. Results are compact document candidates: title,
Denote ID/path, description, and short snippets. Read the chosen path; add
excerpts only after choosing a session hit.
- Two passes, not one. A first query in GLG's own abstract phrasing
retrieves the neighborhood, rarely the canonical hit. Read the top candidates
for concrete handles — project names, file names, coined terms, commands —
and search again with those. This is the normal shape of the tool, not a
recovery from a bad query: an independent GPT-bot run reached the wrong
neighbor on pass 1 and the exact source turn on pass 2 (andenken#10,
2026-09-03). Rule 4 forbids re-querying the same abstraction; this rule
requires re-querying with what pass 1 taught you.
Corollary: a long natural-language sentence is the weakest possible pass-1
query. It also triggers the widest dictcli expansion, and wide expansion
measurably hurts — the same run saw
["salvation","saving","rescueing"]
attached to a query about session addressing (andenken#12). Keep pass 1
short and conceptual; put the length into pass 2's concrete terms.
- Scores rank; they do not certify. Scores are uncalibrated within-query
signals. Session and MD score distributions are not comparable. Nothing in
the ranking is recency — the session axis applies no temporal decay
(
recencyHalfLifeDays: 0 at cli.ts:255 and index.ts:571, and
retriever.ts:365 short-circuits at <= 0; read 2026-09-03). A hit from
2026-04 can outrank one from an hour ago on merit alone. When recency is
what you actually want, say so with --mode recent or --date-from, never
by assuming the top row is the newest.
- People/existence need exact proof. Semantic search proposes candidates;
denotecli confirms the exact room/person before you assert or link it.
- Count documents, not rows. Repeated chunks are one document, and the
session axis is chunk-dense: 75,267 chunks over 1,609 files, one session
reaching 1,382 (measured 2026-09-03 from
andenken/data/session-manifest.json).
Long turns are split into numbered parts rather than truncated at 2K, so
several rows from one turn is the normal shape, not a ranking signal.
Explore results need distinct useful documents; a narrow lookup may favor one.
- Know the limits. No production track has automatic Kiwi stem enrichment;
dictcli expansion needs Hangul. MD
indexedAt is export mtime, not the note
date. Confirm temporal claims with timeline. A question-shaped query
retrieves its own echo — asking "남은 작업 뭐지" returns the turns where
that question was asked, not the answers (andenken golden, 2026-09-03,
inherited). That is the concrete case behind rule 5 and AGENTS.md's
two-step strategy.
An unfiltered session search may append labeled MD fallback rows when session
hits are thin. Keep tracks separate and never compare their scores.
Session options
--source pi|claude · --date-from ISO · --date-to ISO ·
--project a,b · --role user,assistant,compaction · --session-file PATH ·
--session-file-contains TEXT · --mode semantic|hybrid|recent ·
--with-excerpt · --excerpt-limit N.
recent is a timestamp-DESC stored scan with no embedding/BM25/dictcli call.
These options are for search-sessions; search-md accepts query, limit, and
--full only. Convert natural-language KST dates to ISO in the caller.
Device axis — one index, two machines
The session index is built from the gathered corpus, not from one machine's live
store, so results mix thinkpad and oracle work and a query from either machine can
recover the other's. Measured 2026-09-03 by reading
andenken/data/session-manifest.json: 1,609 of 1,609 indexed paths are corpus paths
(oracle 1,017 / thinkpad 592), zero live-store paths.
- There is no
--device option here, by design — no schema gained a device
column. The device is the segment after the corpus root in the returned file
(~/repos/gh/session/<device>/…). Read it there when provenance matters.
- The device says where a session was collected, not where it was created (the
machines exchanged an
rsync -a with mtimes preserved). Label with it; never rank
or date with it.
- The returned
file is exactly what session-recap --session-file takes — that
seam is the reason no path is ever assembled by hand. session-recap --device <name> is the place a device filter exists.
- A hit whose path lives under the other device is normal now, not a bug report. Say
which machine it came from when you quote it.
Boundaries
- A day with no semantic hit is not an empty day: use
timeline / day-query.
- Missing cwd, parent/child, or garden identity is not inferred: use
entwurf-peek for the canonical meta-record join.
- Production org embedding is disabled: use
denotecli for exact/raw ~/org.
reindex --force is destructive and paid-remote gated. Prefer the
memory-sync skill; full rebuilds require the human cost gate.