1. Executive Summary
CSM — Cross-Session Memory — is continuity infrastructure for a coding agent, an OpenCode plugin with a Codex MCP bridge beside it. It captures without a language model and can say, per item, why something did not reach the context window. Its weakness is the Living State tier on top: beliefs, candidate review and capability confidence each compute carefully and terminate in a state no code path writes, a table nothing fills, or an exit code read as evidence.
It is large, and the size is not padding: memories, experience packets, an append-only operational ledger, a work ledger of file edits, a self-model, belief candidates, checkpoints, goals, a context cache, injection telemetry and recall telemetry are all distinct stores with distinct lifecycles.
The design commitment that sets it apart: nothing on the
write path calls a language model. The only outbound request in
the entire runtime is an embedding call to Ollama or OpenAI
(src/embedding-provider-client.ts:9). Extraction is a
deterministic distiller that groups tool calls and stamps
extractionMethod: 'deterministic'
(src/memory-extractor.ts:161); classification is a set of
regexes; belief promotion is five numeric threshold checks. A system
this large that never asks a model to summarize anything is rare here,
and it buys real properties — no extraction cost, no extraction
hallucination, and a store you can reason about by reading SQL.
Two mechanisms stand out. The first is per-item injection
provenance: context_injection_items
(src/schema/context-injection-telemetry-schema.ts:26)
records every candidate considered for the re-entry block with a
position, a selection_score, a
disposition of injected / trimmed
/ omitted, and a selection_reason_code that
includes budget_trim, layer_budget_exhausted
and filter_rejection. Most systems in this atlas can tell
you what they injected. This one can tell you what it nearly
injected and which budget line killed it. The second is the work
ledger: every file edit is stored with before/after content
hashes and a line-hash multiset, and evaluateSurvival
(src/work-ledger-lineage.ts:37) later re-reads the file on
disk to classify the change active,
partially_superseded, superseded, or
reverted. It is a memory of work that gets checked against
the artifact it claims to have produced.
The weakest parts are not in the mechanisms but in what connects
them. Merge and archive maintain superseded_by,
superseded_at, archived_at and an
archive_reason, and the shared WHERE builder all three
search lanes call opens with superseded_by IS NULL and
archived_at IS NULL
(src/hybrid-search-sources.ts:13-37), as do the fallbacks,
list, cascade and the evidence block. One reader was left out. The
lesson-trigger cache selects up to 50 lessons with neither those
predicates nor a project filter and injects all of them on every turn
(src/lesson-trigger-cache.ts:74-80), beside a lessons query
in the same stage that carries both. That reader is why
scope_enforced is withheld; audit_log and
negative_eval are the two marks.
The more expensive disconnect is in the belief tier.
buildBeliefsLayer
(src/reentry-layers-secondary.ts:42) — the layer that puts
the Living State's consolidated beliefs into the injected block —
filters belief.status === 'promoted', and no code
path in the repository ever writes 'promoted'. The
belief consolidator runs every 120 seconds, maintains confidence and
uncertainty per claim, counts contradictions, and has a committed schema
migration to double precision so that subnormal decay values survive.
Everything it produces is candidate or stale,
so the layer renders "No consolidated beliefs yet." on every turn,
permanently. The same shape appears in memory_candidates:
the table is created, indexed, selected from, updated, cleaned up, and
has a one-off dedup script committed against it — and no INSERT
exists anywhere in src/, scripts/ or
test/.
And the AgentBook — the operational ledger whose whole job is to
answer "what is happening in this project right now" — declares 25 event
types (src/agentbook-types.ts:1) of which its one and only
writer (src/hooks/tool-execute.ts:154) can produce seven.
decision, verification_evidence,
blocker_identified, goal_achieved,
user_correction and milestone are never
emitted by any code path. The result is visible in the repository's own
committed AGENTBOOK_STATE.md: after 5,111 events across 49
sessions, the turn-1 front page reports no active goal, no phase, "No
active blockers or known failures", and a Recent Work section in which
eight of ten entries are truncated dumps of CSM's own tool output. The
agent's autobiography is mostly a record of it reading its own
memory.
2. Mental Model
CSM does not have one notion of memory; it has four, layered, with different epistemic standing. Reading them as one pipeline is the fastest way to misunderstand the system.
Durable memory (memories) is the
semantic tier: eleven types plus self_continuity, each row
carrying importance, confidence,
emotion, source, tags, extracted concepts, and
a provenance block in metadata. Experience
packets (experience_packets) are the episodic
substrate — one row per tool execution, error, milestone, decision or
checkpoint, holding signals and internal_state
JSON. AgentBook events are the operational journal:
append-only, project-scoped, summarized in ranges and projected into a
single current-state row. Work-ledger changes are the
physical record: one row per file edit, hashed.
How a thing becomes a belief
There are two independent promotion routes and they meet in the same table.
The distiller route is fast and effectively
unguarded. ToolDistiller groups a session's tool calls;
autoDistill
(src/hooks/tool-execute-memory.ts:62) writes a
distilled_summaries row and hands the groups to
MemoryExtractor.extractFromDistilledSummaries. Each group
becomes a candidate with a hardcoded confidence of 0.92
(src/memory-extractor.ts:152), which clears the default
autoApproveThreshold of 0.9
(src/config-defaults-base.ts:56) and is written straight
into memories. determineInitialStatus declares
a 'rejected' return in its signature that the function body
cannot produce, and the pending state it can
produce goes nowhere: saveCandidateAsMemory writes to
memories or does nothing, and
memory_candidates — the table holding the reviewable queue
— has no INSERT anywhere in the repository. The one function that would
have populated it, extractFromTurns
(src/memory-extractor.ts:51), has no callers, and stamps
extractionMethod: 'llm' on output produced by a keyword
if-chain.
The belief route is the careful one.
CandidateGenerator derives candidate_belief,
candidate_preference, candidate_worldview,
candidate_capability and friends into
memory_candidate_queue, keyed by a dedup_key
with a partial unique index over pending rows.
BeliefPromotionEngine.evaluateCandidate
(src/belief-promotion.ts:207) then applies five gates in
order: confidence, reinforcement count, zero
contradictions, evidence-reference count, and distinct source
sessions — the last computed by joining source_packet_ids
back to experience_packets.session_id
(src/belief-promotion.ts:297). A contradicted candidate
does not fail; it returns needs_review, which is a
different answer from "skip". Every decision carries a
thresholdChecks object recording actual-versus-required for
each gate, so a promotion report explains itself.
Three caveats matter. The engine is disabled by
default (CSM_BELIEF_PROMOTION_ENABLED=false) and
dry-run by default. And minSessions
defaults to 1
(src/config-defaults-continuity.ts:112), so the
session-diversity gate — the most interesting of the five, and the one
that distinguishes a pattern from a coincidence — is a no-op unless an
operator raises it.
A parallel belief_knowledge_store holds
preference/opinion/worldview claims with confidence,
uncertainty, contradicted_count and a
status of
candidate | promoted | rejected | stale. Reinforcement
nudges confidence up by 10% of the remaining gap; contradiction raises
uncertainty by 15% of its gap; crossing uncertainty ≥ 0.7 with more than
two contradictions marks the belief stale
(src/belief-knowledge-store.ts:195). Of the four declared
states, only candidate and stale are
ever written — no code path sets promoted or
rejected. That is not a cosmetic gap, because the one
consumer that reads the field, buildBeliefsLayer, admits
only promoted. The belief tier is therefore a closed loop:
it computes, it decays, it contradicts, and nothing it produces can
reach the agent.
How a belief stops being one
Four exits, and they are unequal.
Supersede. MemoryMerger groups by
LOWER(TRIM(content)) — exact duplicates only, no fuzzy
matching — sets superseded_by on the losers, and appends a
row to memory_merges
(src/merge-tool.ts:189-202). Non-destructive and
audited.
Archive. archive-superseded-duplicates.ts and
archive-tiny-junk.ts stamp archived_at,
archive_reason, archive_batch_id,
archive_source and archive_note, with an
explicit un-archive path that sets them back to NULL. Batch-reversible,
which is better than most.
Expire. cleanupExpiredMemories
(src/memory-manager-base.ts:963) is a real hard delete:
per-project, dry-run unless apply: true, capped at 1,000
rows by default and 10,000 absolute, transactional, and it emits a
memory.retention_cleanup event.
Promote. lesson-auto-promotion.ts is the
best-gated exit. A memory candidate becomes a durable lesson only if it
passes four conditions stated in the module docstring — at least
minRecall recall events, recall in at least
minSessions distinct sessions, a content-quality
heuristic, and no existing lesson with the same content hash. A regex
list refuses the noise classes by shape before any of that:
^\[modified\], ^Tool used:,
^Command executed:, ^\[git\] and the rest.
What survives is tagged auto-promoted and stamped
metadata.auto_promoted = true "so they can be audited
and reverted", and the source candidate is marked
applied. Cross-session recall as the promotion signal is
the good idea here: a lesson earns durability from having been useful
more than once, in more than one session, rather than from a score
assigned when it was written. It is wired from
lifecycle-orchestrator.ts:171.
Reject. On paper, a caller of the Codex bridge's reject tool
sets status = 'rejected' with reviewed_by, and
cleanupExpiredCandidates
(src/memory-extractor.ts:591) deletes rejected
candidates after seven days. Two things spoil it. Nothing is
keyed on the rejected value, so re-observing the same behaviour in week
two regenerates the identical candidate with no record that anyone said
no — this is the closest CSM comes to a tombstone and it is deliberately
temporary storage rather than a durable veto. And the queue it operates
on is never filled, so in practice this exit is unreachable.
Supersede and archive take effect on the read path.
buildWhereClause
(src/hybrid-search-sources.ts:13-37), the single WHERE
builder FTS, vector and entity boost share, starts from
superseded_by IS NULL and archived_at IS NULL
before any scope clause, so the predicates hold in global
and legacy mode too. The vector-only fallback
(src/memory-manager-base.ts:591-592), the text fallback
(:813-814), the evidence block
(src/hooks/system-transform-live-evidence.ts:44-102) and
the onboarding brief (src/agent-onboarding.ts:364,
:717) carry the same pair. The whole set landed on 17
September 2026 in 09e42f98…,
closing issue #99, with governance vetoes and evidence injection
following the same morning.
The reader that missed the sweep is the one with its own cache.
LessonTriggerCache.refresh
(src/lesson-trigger-cache.ts:68-102) selects every lesson
at importance 0.6 or above, 50 at most, with no lifecycle predicate and
no project_id, and injectLessonTriggers pushes
the whole set into the system prompt as
<active_lessons> on every turn
(src/hooks/system-transform.ts:40). The stage before it,
injectDirectMemoryEvidence, selects lessons scoped to the
project and filtered on both columns. So a lesson superseded or archived
in one repository is withdrawn from one block and still instructs the
agent in the next, in every repository. The deep-continuity block, which
runs only when enabled and a trigger keyword appears, has the same gap
in the causal-thread hydrator's temporal-neighbour and decision queries
(src/self-continuity-causal-thread.ts:246-276).
Diagram source
%% caption: corrections reach every read path but the lesson-trigger cache, and the promoted status the beliefs layer requires has no code path that writes it
stateDiagram-v2
[*] --> Packet: tool.execute.after hook
Packet --> Distilled: deterministic distiller, confidence fixed at 0.92
Distilled --> Memory: auto-approved, no reviewable queue exists
Packet --> Candidate: candidate generator
Candidate --> Memory: five promotion gates, engine off by default
Candidate --> NeedsReview: contradicted at least once
Candidate --> Belief: belief consolidator, every 120 seconds
Belief --> Stale: uncertainty over 0.7 and three contradictions
Belief --> Promoted: unreachable, no code path writes this status
Promoted --> Injected: the beliefs layer admits only promoted
Memory --> Superseded: exact-content merge
Memory --> Archived: archive pass sets a flag
Memory --> Deleted: per-project TTL cleanup
Memory --> Retrieved: hybrid search, both lifecycle predicates
Retrieved --> Injected: re-entry block, 2100 characters
Memory --> LessonCache: lessons at importance 0.6 or more
Superseded --> LessonCache: no lifecycle predicate in the cache
Archived --> LessonCache: no lifecycle predicate in the cache
LessonCache --> Injected: active_lessons, every turn, every project
note right of Injected
Superseded and archived rows are
hidden from search. Lessons return
through the trigger cache. Beliefs
never reach the agent at all.
end note3. Architecture
CSM is a library that runs inside the agent host.
There is no daemon, no queue and no worker process. Everything — hooks,
tools, background timers, schema initialization — executes in the
OpenCode plugin process or in the csm-mcp stdio
process.
Persistence is PostgreSQL 14/16 with pgvector, or
SQLite. The Postgres path owns 46 tables. Vectors are dual-written: an
embedding column on memories for the
vector-only fallback, and a memory_chunks row carrying the
same vector under an HNSW index with
m = 16, ef_construction = 64
(src/schema/memory-support-schema.ts:14). Full-text is a
generated tsvector column weighting content A, tags B and
metadata C (src/schema/memory-support-schema.ts:44).
Schema management is unusually disciplined for a
project this size. A csm_schema_migrations ledger with
per-migration checksums (src/schema/migration-ledger.ts)
fails fast on an unknown future migration or on a ledger belonging to
the other provider. docs/SCHEMA_SUPPORT_MATRIX.md names
each supported upgrade path and the test that proves it, and states
plainly that rollback restores a backup rather than reversing DDL.
Background work is in-process timers, not jobs: a
2-second debounced documentation flush
(src/hooks/tool-execute-memory.ts:11), a 120-second belief
consolidation interval, self-model replay, and the distiller flush.
Nothing survives a host crash except what already reached the database.
There is no periodic pass that re-reads or rewrites the whole store,
which is a real operational virtue — the background token bill is zero
because there is no background model call at all.
External dependencies are one embedding provider and
a database. A third is optional and is a separate project by the same
author: CSM_LIVING_MIND_URL
(src/hooks/system-transform-live-end.ts:41) names a Living Mind
Cortex server, and when it is set every system-prompt transform
issues GET /api/agent/context with a 500 ms timeout and
skips silently on any failure. Unset, nothing is fetched. Section 8 has
what the block carries.
Deployment and ergonomics
The full path needs PostgreSQL with pgvector plus a running
Ollama (or an OpenAI key). First run is
npx csm-init then npx csm-doctor --online,
which checks package, Node version, strict config, security baseline,
database, migration history and embedding model, and is documented not
to include credentials or memory content in its output.
The SQLite "core mode" is narrower than the README's feature table
suggests, and the difference is worth stating in retrieval terms rather
than feature terms: searchMemories skips vector
search entirely on SQLite
(src/memory-manager-base.ts:528) and
checkFtsAvailable returns false
unconditionally for that dialect
(src/hybrid-search-sources.ts:171). What remains is
content LIKE '%query%' ordered by importance and creation
date. That is a usable local mode; it is not the hybrid retrieval the
same document describes two sections earlier.
Nothing is human-readable except AGENTBOOK_STATE.md and
.csm/continuity-snapshot.json. Repair means SQL. Against
that, the backup/restore drill
(npm run drill:backup-restore) is committed and wired into
the release gate.
4. Essential Implementation Paths
Capture. tool.execute.after
(src/hooks/tool-execute.ts) → logToolUsage
(src/hooks/tool-execute-memory.ts:111) → an AgentBook event
via buildAgentBookToolEventInput
(src/agentbook-tool-event.ts:72) and an experience packet.
Tool output is classified by classifyToolEvent, and the
event summary is ${tool}: ${output.slice(0, 200)}.
Write. MemoryManager.saveMemory
(src/memory-manager-base.ts:185) in strict order: default
provenance metadata → project-ownership verification against
sessions → transcript dedup on
metadata.messageId → redaction of content, metadata and
tags → per-type token quota → concept extraction → embedding generation
→ INSERT → dual-write to memory_chunks →
memory.created event → graph link building.
Extraction. autoDistill
(src/hooks/tool-execute-memory.ts:62) →
MemoryExtractor.extractFromDistilledSummaries →
saveCandidateAsMemory. The lesson text is shaped by
makeActionableLesson
(src/memory-extractor.ts:198), which prefixes
Avoid this — when no imperative verb is detected.
Retrieval. MemoryManager.searchMemories
(src/memory-manager-base.ts:510) →
hybridSearch (src/hybrid-search.ts:26), which
runs vectorSearch, ftsSearch and
entityMatchBoost concurrently, fuses with
reciprocalRankFusion at RRF_K = 60, normalizes
each channel to its own max, applies weights, and drops near-duplicates
at Jaccard ≥ 0.85 (src/hybrid-search-ranking.ts:83). Three
fallback tiers follow on failure, each recorded in
memory_recall_events with a distinct source
value.
Injection.
experimental.chat.system.transform →
runSystemTransform
(src/hooks/system-transform.ts:32-56), twelve sequential
stages ending in finalizeSystemTransform; the third of them
injects the lesson-trigger cache
(src/lesson-trigger-cache.ts:155-190). The re-entry block
itself is built by ReentryLayerBuilder
(src/reentry-layer-builder.ts) across eight layers and
logged item-by-item through
context-injection-logger.ts.
Correction. MemoryMerger.applyGroup
(src/merge-tool.ts:189),
archive-superseded-duplicates.ts,
archive-tiny-junk.ts,
MemoryManager.deleteMemory
(src/memory-manager-base.ts:491),
cleanupExpiredMemories
(src/memory-manager-base.ts:963).
Work verification.
verifyWorkLedgerChanges
(src/work-ledger-verification.ts:11) takes a per-file
capture lease, re-reads the file, and calls
evaluateSurvival
(src/work-ledger-lineage.ts:37).
Integration. registerHooks
(src/hooks-registration.ts:16) and
createRegisteredToolList
(src/hooks/tool-registry.ts:52) for OpenCode;
src/codex-mcp-server.ts with
src/codex-bridge-extra-ops.ts for Codex.
5. Memory Data Model
memories
(src/schema/memory-table-schema.ts) carries a twelve-value
memory_type CHECK, importance and
confidence as bounded floats, an emotion enum,
a tags array, linked_memory_ids, a JSONB
metadata, four timestamps, access_count, five
archive columns and a self-referencing superseded_by.
Scoping is single-axis, and strict on the search
path. Everything hangs off project_id, which is
the workspace directory. There is no user, tenant, agent or team axis —
CSM assumes one operator. What it does with that axis is careful:
saveMemory refuses a write whose declared project disagrees
with the session's recorded project
(src/memory-manager-base.ts:215-218), and every public tool
is constructed with its projectId bound at registration so
the agent cannot pass one (src/tools.ts:71).
Provenance is defaulted onto every row that does not
supply it: source_kind, evidence_strength,
source_session_id, source_agent_id,
source_model_id, source_surface
(src/memory-manager-base.ts:190-199). Promoted memories
supply their own instead — source_kind: 'belief_promotion'
and evidence_strength: 'derived_pattern', alongside the
source packet ids and the session count that justified the promotion —
so a derived belief is distinguishable from a directly observed one at
query time. This is the one place CSM does encode epistemic difference;
it just encodes it in JSONB rather than in a column anything filters
on.
Temporal modelling is record-time only.
created_at, updated_at,
accessed_at, superseded_at,
archived_at, last_reinforced_at all describe
when CSM learned or changed something. Nothing records when a fact
was true. A preference that held for three months and then
changed is representable only as two rows, one of them superseded — with
no interval on either.
The other stores are cleanly separated rather than overloaded:
experience_packets for episodes,
agentbook_events/summaries/current_state
for operations, work_ledger_changes for physical edits,
self_model_capabilities for capability confidence,
belief_knowledge_store for stances,
checkpoints and goals for task state,
memory_quality_scores for a scored band per memory. That
separation is the main reason 46 tables is legible rather than
sprawling.
Two readers break the scoping model. The lesson-trigger cache reads
memories itself with no project predicate and injects what
it finds (section 2). And
self_model_capabilities has no
project_id, and
SelfModelUpdater.loadPackets
(src/self-model-updater.ts:136) reads
experience_packets with no project filter and no limit.
Capability confidence earned in one repository is derived from, and
displayed in, every other.
6. Retrieval Mechanics
Four signals, fused. Vector similarity over pgvector cosine distance;
Postgres websearch_to_tsquery full-text with
ts_rank_cd; an entity boost that scores 2.0 for a content
match, 1.8 for a match inside metadata.extracted_concepts,
and 1.5 for a tag match
(src/hybrid-search-sources.ts:146-148); and recency with a
168-hour half-life. Default weights are vector 0.35, text 0.25, entity
0.35, recency 0.05 — an unusually high entity weight, and a defensible
one for a coding agent where the query is often a literal symbol or
path.
The fusion is a two-stage normalize: RRF within each channel, then
min-zero max-one normalization per channel before weighting
(src/hybrid-search-ranking.ts:39-43). Recency is clamped
rather than normalized, so its contribution is absolute rather than
relative to the candidate set — which is the right choice, since
normalizing recency would make the newest result in every set score 1.0
regardless of age.
Retrieval is tool-mediated for search and automatic for
injection. The agent calls csm_memory_search; the
re-entry block arrives without being asked. The search tool returns
150-character previews with score, type, importance and access count —
enough to decide, not enough to use, which pushes the agent toward a
second fetch.
Budgeting is tight where it matters. The whole
re-entry block is capped at maxChars: 2100
(src/reentry-contract.ts:95) — roughly 525 tokens —
allocated across eight layers with per-layer budgets and priorities,
identity and constraints marked
neverTrim. Compared with systems in this atlas that inject
several thousand tokens of "relevant memories" unbounded, a
2,100-character ceiling is a deliberate and defensible constraint.
Failure modes, in order of how much they would bother me:
- Retired lessons return through the trigger cache.
Covered in section 2: search excludes superseded and archived rows, and
<active_lessons>does not, across projects. It is the highest-value fix in the codebase and it is three predicates in one query. - Jaccard-0.85 dedup is unigram set overlap, so two memories that state opposite conclusions in similar vocabulary ("always use X" / "never use X") are near-identical under it and one is silently dropped.
- SQLite degrades to substring matching without saying so at the call site.
- Entity boost uses
ILIKE '%query%'on the whole query string, so multi-word queries rarely trigger the channel that carries 0.35 of the weight.
7. Write Mechanics
Writes are agent-triggered or hook-triggered, never model-mediated. There is no extraction prompt in this repository because there is no extraction model.
Redaction runs before persistence, embedding,
concept extraction and chunking — the ordering is explicit in the file
header (src/redactor.ts:1, "Redact before storage,
never after") and enforced in saveMemory before any
other processing. Secrets, emails, phones, IPs and URL credentials are
redacted by default; workspace paths are normalized to
[WORKSPACE]/src/foo.ts rather than destroyed, which
preserves the one kind of PII a coding agent actually needs. Metadata
and tags are redacted structurally too, and the audit records counts
only, never values.
The per-type quota is the one lossy step and it is honest about it.
applyTypeQuota (src/memory-type-quota.ts:26)
compresses over-budget content to signal lines — lesson is
exempt unconditionally, error-marked content is exempt,
episodic gets 200 tokens — and saveMemory logs
a warning stating that "The original text is NOT recoverable"
and naming the type to use instead. Destructive, documented, and pointed
at the workaround.
Deduplication is layered: an in-flight check on
metadata.messageId for transcript rows, a unique partial
index as the backstop with a unique-violation handler that returns the
existing row rather than failing the capture
(src/memory-manager-base.ts:336), an
md5(compressed) unique index on distilled summaries, a
partial unique index on pending candidates by
(candidate_type, dedup_key), and the exact-content merge
pass. This is what a system built by someone who has actually run two
plugin instances at once looks like.
Conflict handling exists only at the belief tier: contradiction
increments a counter, raises uncertainty, and eventually marks a belief
stale. Two contradictory memories rows simply
coexist — and since the belief tier's output never reaches the agent, in
practice conflict handling does not exist on any path the model can
see.
Operational cost
The write path is synchronous and blocks on the embedding
call. saveMemory awaits
this.embeddings.generate(contentToProcess) before the
INSERT (src/memory-manager-base.ts:303), so every capture
costs one round trip to Ollama or OpenAI on the agent's critical path.
An embedding failure is caught and logged, and the row is stored with a
NULL embedding — recoverable later via
csm_memory_backfill_embeddings, but invisible to vector
search until then. There is no LLM call, so the cost is milliseconds
rather than seconds; the lag before a memory is retrievable is
effectively zero.
No background pass re-reads or rewrites the store. Merge, archive and quality scoring are all operator-invoked tools, not schedules.
On the read path, the per-turn cost is the concerning number. Twelve
injection stages run inside
experimental.chat.system.transform on every
request (src/hooks/system-transform.ts:37-55),
appending re-entry, advisories, lesson triggers, governance notes,
session context, self-continuity and a compiler status line to the
system prompt. A system prompt that changes every turn
invalidates the provider's prompt-prefix cache every
turn. The re-entry block is small; the cache miss it forces is
not, and nothing in the repository measures it.
8. Agent Integration
Eleven OpenCode hook keys
(src/hooks-registration.ts:37-47): event,
chat.message, permission.ask,
tool.execute.before, tool.execute.after,
tool, dispose, and four under
experimental.* — chat.system.transform,
chat.messages.transform, session.compacting,
compaction.autocontinue. The deepest and most valuable
integration points are all experimental, which is a standing coupling
risk: the injection architecture depends on OpenCode APIs its own
namespace marks as unstable.
Roughly fifty tools register in the Postgres path across memory,
governance, Living State, AgentBook, continuity, checkpoints/goals and
context cache. The agent has substantial agency — it can save, search,
delete, merge, generate candidates, promote beliefs, run governance
reports and export a wiki — but not over scope: projectId
is bound at registration and absent from every tool's argument schema,
verified by test/public-memory-tool-isolation.test.ts.
The Codex MCP bridge exposes a comparable surface
over stdio, and is the only surface that reaches the candidate review
tools — memory_candidate_list,
memory_candidate_approve,
memory_candidate_reject
(src/codex-mcp-extra-tools.ts:32). The equivalent OpenCode
tools are written and exported in src/tools.ts:489 and
never registered, so on the plugin path they exist in
the binary and cannot be called.
That asymmetry turns out not to matter, which is the more interesting
fact. memory_candidates has no writer: every reference in
src/, scripts/ and test/ is a
SELECT, an UPDATE, a DELETE, a
CREATE TABLE or an index, and the only function that
constructs a pending candidate is the uncalled
extractFromTurns. So the Codex bridge's approve/reject
tools are live code over a table nothing fills.
The human_review mark is withheld on that basis and on a
second one that would hold even if the table filled: the approve verb is
registered on the Codex MCP server, the same tool surface the agent
writes memories through, so the producing agent can clear its own queue.
No commit on any branch, from the first on 25 June 2026, contains an
INSERT into the table. Yet the one-off
scripts/dedup-candidates.mjs, committed on 29 June 2026,
counts and de-duplicates rows in it, so the maintainer's database was
filled by code that predates this repository's history.
Turn 1 is handled outside the plugin entirely:
AGENTBOOK_STATE.md is listed in opencode.json
instructions, so the front page is read by the host before
any hook runs. Solving cold start with a file the host already loads is
a good, cheap idea — it works even when the plugin fails to
initialize.
One integration behaviour deserves naming. When a user's message
matches /only\s+<agent_reentry_context>/i and similar
patterns, CSM latches a "source-only" mode for three minutes, disables
every csm_* tool plus bash, read,
grep, edit, write and nine
others, and injects an instruction block
(src/hooks/reentry-source-only.ts:32-47). Restricting tools
so an answer is provably grounded in the injected block is a legitimate
and clever guarantee. But the same block instructs the model: "Do
not mention blocked tools, failed tools, guards, permissions, shell
attempts, or hidden implementation details" — the runtime tells the
agent to conceal from the user that its tools were taken away. And the
first line prescribes a verbatim opening sentence about git history that
is asserted in test/phase-7b-system-transform.test.ts:124.
A product's system prompt containing the exact expected answer to one
evaluation question is a smell worth flagging regardless of intent.
The Living Mind block is the one input CSM injects
from outside its own store. With CSM_LIVING_MIND_URL set,
injectLivingMind
(src/hooks/system-transform-live-end.ts:38-52) fetches
/api/agent/context in the last stage of every transform and
renders a <living_mind_context> block of cognitive
stance, urgency, creative pressure, circadian phase, energy, pain, load
and any phase-blocked task types (:17-36). The server is NovasPlace/living-mind-cortex,
matched on the route rather than the name. At f8bdb805…
its FastAPI router mounts /api/agent
(api/agent_gateway.py:36), serves GET /context
(:326-388) on port 8008 (api/main.py:702), and
returns every field the block reads but one under the same names. CSM's
first commit, 672a78a6…
of 25 June 2026, fetched
http://localhost:8008/api/agent/context with no timeout
under the comment "Living Mind Cortex: inject cognitive state";
3b8bfa39…
moved it behind the variable on 14 July 2026.
Three consequences follow from the match. The block carries no
memory: the route also returns relevant_memories, and CSM
renders none of it. One line never renders, because CSM reads
hormones.dominant_emotion and the route's
hormones object holds five numbers, with the dominant
emotion returned as recall_bias instead
(api/agent_gateway.py:345, :378-384). And the
phase gate is wall-clock: from 22:00 to 05:00 the server reports
night, and CSM tells a coding agent
Phase blocked: execution, heavy_refactor, research
(api/agent_gateway.py:87-92,
state/circadian.py:150-157). The server's
ARCHIVE.md marks it a fossil as of 5 August 2026 and names
CSM as the memory organ its ideas moved to; the hook is unchanged on
every CSM branch.
9. Reliability, Safety, and Trust
Provenance is the strongest column. Every memory
carries source kind, evidence strength, session, agent, model and
surface. classifyValueClaim
(src/value-source-guard.ts:45) marks a claim
known only when a stored memory backs it and its
provenance is one of
transcript | tool_trace | file_diff | user_supplied at
direct_original strength and confidence ≥ 0.7 —
otherwise inferred. detectUnlabeledInferences
scans generated text for hedges like "seems to prefer" and "based on
patterns" and flags them as unlabelled inference. That is a memory layer
that can represent uncertainty and says so.
What it cannot represent is epistemic status on a
memory. The memories table has a
confidence float and no status column at all — no
candidate, no verified, no rejected — so a promoted belief and a
directly observed fact differ only in their provenance metadata, not in
anything the read path can filter on. The two places a discrete status
does exist both fail to close, and both are worth stating
exactly, because each looks closed from one end.
The belief status has two writers and neither writes the
value four readers want.
belief_knowledge_store.status is
CHECK (status IN ('candidate', 'promoted', 'rejected', 'stale')).
Three statements in src/ touch the table: one
INSERT that writes 'candidate', and two
UPDATEs that write 'stale'. Nothing anywhere
writes 'promoted' or 'rejected'. Four read
paths filter on 'promoted' —
reentry-layers-secondary.ts:42 and
reentry-layers-state.ts:51 building the re-entry brief,
agent-onboarding.ts:430, and
continuity-resilience-report.ts:438 counting them for the
health report — so the belief section of a re-entry brief is empty by
construction and the health report reports zero as a fact about the
system rather than about the data.
The trap for a reader is the filename.
src/belief-promotion.ts exists, is 400 lines, is wired, and
logs Belief promotion: N promoted — and what it promotes is
rows of memory_candidate_queue into memories.
It never touches belief_knowledge_store. A grep for
"promotion" finds a live module; a grep for what writes the column finds
nothing.
And memory_candidates is a table with no
INSERT. Its
pending | approved | rejected | auto-approved | archived
machine is declared in schema/core-schema.ts:52, given four
indexes, and then selected from (memory-extractor.ts:499,
:574), updated (:531, :599) and
swept (:593, memory-manager-base.ts:1015) —
five statements and a TTL delete over rows nothing creates. It is a
different table from the live memory_candidate_queue, which
candidate-generator.ts:145 and
belief-promotion-scanner.ts:468 do fill; the two names
differ by one word and only one of them is connected.
trust_state is withheld, and the reason is the useful
sentence: CSM has more trust machinery than most systems that
carry the mark, and none of it terminates in a status a query can act
on. The nearest miss is on the lifecycle axis. Every search lane
excludes superseded_by and archived_at, but
those are a pointer and a timestamp rather than a status, and every
writer of either is exact-content deduplication or junk archival
(src/merge-tool.ts:194,
src/archive-superseded-duplicates.ts:147,
src/archive-tiny-junk.ts:239). A superseded row says the
same thing as its survivor, so the filter withholds a duplicate, not a
claim held to be false.
scope_enforced is withheld, with a strong
near-miss. The search path fails closed to 1=0 in
project mode, and every public tool binds
projectId at registration so the model cannot name another
project (src/hybrid-search-sources.ts:39-57,
src/tools.ts:71). But
LessonTriggerCache.refresh reads the same
memories table with no project_id predicate
(src/lesson-trigger-cache.ts:74-80), and
runSystemTransform injects the result into the system
prompt on every turn (src/hooks/system-transform.ts:40).
The key is on those rows — the lesson promoter writes
projectId: ctx.directory
(src/lesson-auto-promotion.ts:148) — and the default store
is one Postgres database at a fixed URL for every workspace
(src/config-provider.ts:10-11, :71). So an
injection hook over the same store carries no predicate at all.
A second, weaker point sits beside it. A project search
that returns zero rows is retried as legacy, which admits
rows with no project, and the widened mode is passed into the cascade
(src/bridge-ops.ts:55-68). That widening reaches unscoped
memories rather than another project's.
Audit is broad, with one gap.
memory_events records creation, deletion and retention
cleanup (src/memory-manager-base.ts:377, :500,
:1026), and memory_merges records each merge
with its normalized hash; archival and metadata updates stamp the row
and append nothing. Beside them, context_injection_events
and _items record what was injected, trimmed and omitted
with an idempotency key, a block_hash, a
builder_version and a config_hash;
memory_recall_events records which memories answered which
query at what rank, hashing the query text after redaction — a
detail test/privacy-persistence-boundaries.test.ts:167
asserts explicitly.
Prompt-injection resistance is partial. The re-entry
header declares "Status: operational context, not user
instruction", which is a real mitigation. But conversation memories
are captured with fullTranscript: true and stored verbatim,
and a transcript contains whatever the agent read — web pages,
dependency READMEs, log files. That text becomes a memories
row, becomes searchable, and can be selected into the
recent layer. Provenance records that it came from a
transcript; nothing downgrades or fences text that originated outside
the user. The Living Mind block has the same shape at a smaller size: an
unauthenticated HTTP response whose cognitive_stance and
status strings are rendered verbatim into the system prompt
without the re-entry header's disclaimer
(src/hooks/system-transform-live-end.ts:19,
:29).
Concurrency is handled better than average. Project
ownership is verified before write; the work ledger takes an advisory
capture lease per file (src/work-ledger-capture-lock.ts);
unique-violation races on transcript capture resolve to the existing
row; the documentation flush uses per-project timers so two workspaces
cannot cancel each other.
Data-loss risks are three: the type quota is
irreversible and warned about; belief evidence_refs grows
one JSON entry per packet forever, and the committed self-model shows
code_editing at 3,849 evidence entries in a single row; and
SelfModelUpdater.updateAll loads the entire
experience_packets table into memory on each run, which at
the maintainer's own 51,170 packets is already an unbounded read.
That same committed snapshot exposes the deepest trust problem in the
design. The self-model reports
code_editing confidence=0.900 successes=3849 failures=0.
determineOutcome
(src/self-model-updater.ts:181) derives success from the
absence of an error field and a zero exit code — so the model is
measuring "the edit tool returned", not "the edit was
correct". 3,849 edits with zero failures is not a capability
estimate; it is a tautology with a confidence attached. The 0.9 ceiling
and the diminishing-returns rate after 20 observations are thoughtful
guards against the number being too high; they do not address
the observable being the wrong one.
10. Tests, Evals, and Benchmarks
The suite is large and real: 1,782
test(/it( call sites across 212 test
files. The committed full-test-output.txt records
808 passing tests across 172 suites, zero failures — a
narrower run than the call-site count, presumably one where
Postgres-gated suites did not execute. The README's claim of "more than
1,500 automated tests" is supportable at the call-site level and not by
that artifact; the two measure different things. The artifact was last
written on 8 July 2026, so it is a snapshot rather than a check. I
inspected these artifacts; I did not run the suite. No paper is cited
from the README or docs/.
What is well covered: schema migration and idempotent replay,
redaction across every persistence boundary, project isolation for tools
and candidates, the context-injection contract and telemetry,
work-ledger lineage and survival, belief promotion thresholds, archive
and merge dry-run behaviour (including
assert.equal(updateSeen, false, 'dry-run must not issue UPDATE')),
and compaction telemetry.
Negative evidence exists and is explicit. The
strongest case is about corrected material:
test/active-memory-recall.test.ts:43-113 saves three
memories sharing a query phrase, archives one, supersedes another, and
asserts that search, list and cascade each return exactly the live id —
a populated set with the positive control inside the same assertion. It
runs on SQLite; the Postgres vector lane is covered only by a mocked
pool asserting the SQL text (:115-130).
test/privacy-persistence-boundaries.test.ts:174 asserts
assert.deepEqual(missingProjectScope, [], 'project mode without a project ID must fail closed')
— a committed case asserting that material must not be
retrieved. test/memory-candidate-isolation.test.ts asserts
a reviewer bound to one session cannot act on another session's
candidate.
test/benchmark-hybrid.ts is a genuine retrieval harness:
eight seeded memories and five labelled queries — three exact-symbol,
two semantic — comparing hybrid against vector-only weights with an
expected content substring per query. Eight documents is a smoke test
rather than a benchmark, but it is a real one with ground truth, and its
seed data candidly includes a memory describing a past entity-recall bug
in this very code.
What the suite does not cover is reachability, and
that is where its worst defects live. No test asserts that the beliefs
layer can ever render a belief, or that a pending candidate can exist;
both would fail at this commit. And no test file references
LessonTriggerCache, so the one reader the active-memory
sweep missed is the one reader with no case. A suite this thorough
carries no check that a status a reader requires is a status some writer
produces.
Also missing: a retrieval-quality regression over more than eight
documents; a test for SelfModelUpdater at packet counts
where the full-table load matters; and any measurement of the per-turn
injection cost against prompt caching.
11. For Your Own Build
Steal
Log the injection, item by item, with the reason it
lost. A table like context_injection_items —
position, score, disposition, and a reason code distinguishing
budget_trim from layer_budget_exhausted from
filter_rejection — turns "the agent didn't know X" from an
argument into a query. Almost every memory system can tell you what it
injected. Being able to say what came fourth when three fit is a
different class of debuggability.
Verify a memory of work against the artifact it
describes. Store the before/after hash and a line-hash multiset
with the edit, then re-read the file later and classify the change
surviving, partially surviving, superseded or reverted
(src/work-ledger-lineage.ts). Most agent memory records
intent; this records intent and then checks it against the world.
Require session diversity before promoting a pattern to a belief. Counting distinct sessions behind a candidate, not just occurrences, is what separates a habit from a loop — one runaway session can reinforce a candidate fifty times and still be one observation. Set the threshold above 1, which CSM's own default does not.
Redact before every downstream step, and normalize paths
instead of destroying them. The ordering rule at the top of
src/redactor.ts — redact before storage, embedding,
chunking and concept extraction — is the correct invariant, and turning
C:\Users\x\proj\src\a.ts into
[WORKSPACE]/src/a.ts keeps the memory useful.
Return the existing row on a unique-violation race instead of failing the capture. Memory writes are best-effort side effects of someone else's work; they should never surface an error to the agent.
Avoid
Don't let correction bookkeeping and retrieval drift apart,
and count the caches as read paths. A
superseded_by column that a read path ignores is worse than
no column: the governance report says the store is clean, and the agent
keeps reading the duplicate. CSM added the predicate to search, the
fallbacks, list, cascade, graph recall, governance and evidence
injection in one morning, and missed the one component that queries the
table on its own schedule and holds the result for a minute. Grep for
every FROM memories, not for every search function.
Don't derive capability confidence from tool exit codes. "The tool returned 0" is not evidence the work was right, and a self-model that has recorded 3,849 successes and zero failures has proved only that it is measuring the wrong thing. Tie confidence to an outcome someone or something else verified.
Don't declare a vocabulary your writers cannot produce. Twenty-six event types with seven emitters means the schema documents an intention and the data records tool calls. Either write the emitters or shrink the enum — the gap is invisible until someone reads the projection and finds no decisions in it.
Never let a read path filter on a status the write path
cannot produce. This is the same defect three times in one
codebase — a beliefs layer admitting only promoted when
nothing writes promoted, a review queue whose table has no
INSERT, an extractFromTurns with no callers — and each one
fails silently, rendering an empty section rather than an
error. The cheap defence is a startup assertion or a test per enum
value: for every state a reader depends on, prove some writer can reach
it. A dead branch that returns nothing looks exactly like a feature with
nothing to say yet.
Don't record your own memory tooling as project
activity. If the operational ledger logs
csm_memory_list and csm_continuity_report as
events, the recent-work summary fills with the agent reading its own
memory. Exclude the memory system's own surface from the memory system's
own journal.
Don't instruct the model to hide an active guard from the user. Disabling tools to guarantee a grounded answer is defensible; telling the agent not to mention that tools were disabled turns a safety mechanism into a misrepresentation.
Fit
This is a single-operator system, and it is unusually strong
at the parts a single operator actually touches: capture,
retrieval, redaction, injection budgets, migrations, backup. One
project_id axis, no auth, no tenancy, no user model, an
embedding call on the write path, and a database you are expected to
back up yourself. If you are one engineer running coding agents across a
handful of repositories on your own machine and you want continuity that
is inspectable in SQL and costs nothing per capture, that core is among
the most complete things in this atlas, and the migration ledger and
backup drill mean it will survive its own upgrades.
Set your expectations one layer down, though. The Living State tier — beliefs, capability confidence, candidate review, the whole apparatus the README leads with — either terminates in an unreachable state, reads a table nothing writes, or counts exit codes as evidence. None of that will hurt you; it will simply do nothing, quietly, while looking like the most sophisticated thing in the package. Adopt CSM for the continuity runtime and treat the epistemics as unfinished, which at this commit is what they are.
If you are building a product, walk away. Not because of the quality — the engineering is careful and the operational discipline is above this atlas's median — but because the shape is wrong for it. There is no tenancy boundary to extend, the cross-project self-model is a leak the moment two clients share a deployment, four of the eleven hooks are marked experimental by the host, and 57,000 lines with 46 tables is a maintenance surface that assumes its author's context. The two mechanisms to take from it — per-item injection provenance and the work-survival ledger — are each a few hundred lines and are better copied than adopted.
The middle case, a team of five wanting shared agent memory, is the one to refuse outright. CSM has no concept of another person, and the design does not have a seam where one would go.
12. Open Questions
- What is the per-turn latency and prompt-cache cost of the twelve-stage system transform in practice? Nothing in the repository measures it, and it is the number most likely to decide whether this is usable on a large model.
- Does
SelfModelUpdater.updateAllremain viable at the maintainer's own scale? The committed state reports 51,170 experience packets and a full-table load per run; whether that has been felt is not visible from the code. - Why are the OpenCode candidate review tools written but never registered — a deliberate narrowing of the plugin surface, or an unfinished wiring?
- Does the belief-promotion engine get enabled in real use? It is off and dry-run by default, and the most interesting epistemics in the codebase sit behind those two flags — beside a beliefs layer that would render empty even if they were on.
- What filled
memory_candidatesbefore 25 June 2026? The rows the dedup script counts came from code outside this repository's history, and the maintainer's earlier projects are the obvious place to look. - Is the lesson-trigger cache's missing project and lifecycle filter known? The 17 September 2026 sweep covered search, list, graph, governance and the evidence block, which makes this one read as an omission.
Appendix: File Index
Storage and schema —
src/schema/memory-table-schema.ts,
src/schema/memory-support-schema.ts,
src/schema/core-schema.ts,
src/schema/agentbook-schema.ts,
src/schema/context-injection-telemetry-schema.ts,
src/schema/migration-ledger.ts,
src/candidate-schema.ts,
src/experience-packet-schema.ts,
src/belief-knowledge-schema.ts,
src/self-model-schema.ts,
src/work-ledger-schema.ts.
Write path — src/memory-manager-base.ts
(behind the facade src/memory-manager.ts),
src/memory-extractor.ts, src/redactor.ts,
src/memory-type-quota.ts,
src/concept-extractor.ts,
src/hooks/tool-execute-memory.ts,
src/agentbook-tool-event.ts.
Retrieval — src/hybrid-search.ts,
src/hybrid-search-sources.ts,
src/hybrid-search-ranking.ts,
src/hybrid-search-types.ts,
src/priming-engine.ts,
src/recall-telemetry.ts.
Context assembly —
src/reentry-contract.ts,
src/reentry-layer-builder.ts,
src/reentry-layers-primary.ts,
src/reentry-layers-secondary.ts,
src/context-injection-contract.ts,
src/context-injection-logger.ts,
src/hooks/system-transform.ts,
src/hooks/system-transform-live-evidence.ts,
src/hooks/system-transform-live-end.ts,
src/lesson-trigger-cache.ts,
src/hooks/reentry-source-only.ts,
src/agentbook-frontpage.ts.
Trust and governance —
src/belief-promotion.ts,
src/belief-knowledge-store.ts,
src/self-model-updater.ts,
src/value-source-guard.ts, src/merge-tool.ts,
src/archive-superseded-duplicates.ts,
src/archive-tiny-junk.ts,
src/memory-governance-report.ts,
src/quality-scoring.ts,
src/work-ledger-lineage.ts,
src/work-ledger-verification.ts.
Integration —
src/hooks-registration.ts,
src/hooks/tool-registry.ts, src/tools.ts,
src/tool-names.ts, src/codex-mcp-server.ts,
src/codex-mcp-extra-tools.ts,
src/codex-bridge-extra-ops.ts.
Tests and benchmarks —
test/active-memory-recall.test.ts,
test/privacy-persistence-boundaries.test.ts,
test/public-memory-tool-isolation.test.ts,
test/memory-candidate-isolation.test.ts,
test/belief-promotion.test.ts,
test/work-ledger-lineage.test.ts,
test/context-injection-audit.test.ts,
test/archive-tiny-junk.test.ts,
test/benchmark-hybrid.ts,
full-test-output.txt.
Operator-facing — AGENTBOOK_STATE.md,
docs/SCHEMA_SUPPORT_MATRIX.md,
docs/FEATURES.md, SECURITY.md,
scripts/backup-restore-drill.ts,
src/doctor.ts.
Appendix: Recorded Searches
Run from the root of the checkout at the pinned commit.
| Claim | Command | Result at this pin |
|---|---|---|
| Search lanes and fallbacks carry both lifecycle predicates | git grep -n -E "superseded_by IS NULL|archived_at IS NULL" -- src/hybrid-search-sources.ts src/memory-manager-base.ts |
hybrid-search-sources.ts:23-24;
memory-manager-base.ts:568-569, :591-592,
:676-677, :813-814 |
| The lesson-trigger cache filters on neither lifecycle nor project | git grep -n -E "superseded_by|archived_at|project_id" -- src/lesson-trigger-cache.ts |
Nothing |
| The cache is read by the per-turn system transform, which injects it, and by the tool hook, which only logs | git grep -n -E "injectLessonTriggers\(|lessonTriggers\.refresh\(" -- src |
hooks/system-transform.ts:40,
hooks/system-transform-live-evidence.ts:169,
hooks/tool-execute.ts:219, and the definition at
system-transform-live-evidence.ts:164 |
| No test exercises the lesson-trigger cache | git grep -l -E "LessonTriggerCache|lesson-trigger-cache" -- test |
Nothing |
| Nothing writes a promoted or rejected belief | git grep -n -E "INSERT INTO belief_knowledge_store|UPDATE belief_knowledge_store" -- src scripts;
git grep -n -E "'(promoted|rejected)'" -- src/belief-knowledge-store.ts |
One INSERT writing the entry's own status, which is set to
'candidate' (belief-knowledge-store.ts:233) or
'stale' (:196), and two UPDATEs writing
'stale'; the second command returns nothing |
| Four read paths want one | git grep -n -E "'promoted'" -- src |
reentry-layers-secondary.ts:42,
reentry-layers-state.ts:51,
agent-onboarding.ts:430,
continuity-resilience-report.ts:438, plus two schema CHECKs
and the BeliefStatus type |
memory_candidates has no INSERT at the pin or in any
commit |
git grep -n -E "INSERT INTO memory_candidates" -- src scripts test;
git log --all --format="%h %s" -G"INTO memory_candidates[^_]" |
Nothing, and no commit on any branch |
| The approve verb sits on the agent's Codex tool surface; the OpenCode twins are unregistered | git grep -n -E "memory_candidate_(approve|reject)'" -- src;
git grep -n -E "memoryCandidate(List|Approve|Reject)Tool" -- src |
codex-mcp-extra-tools.ts:37, :46 and the
bridge dispatch; the second returns only the three definitions in
tools.ts |
| One AgentBook writer, 25 declared types | git grep -n -E "agentBookEvents\.append\(" -- src;
sed -n "/^export type AgentBookEventType/,/;$/p" src/agentbook-types.ts | grep -c "^ | '" |
hooks/tool-execute.ts:154; 25 |
memory_events covers three memory channels |
git grep -n -E "emitEvent\('memory\." -- src |
memory.created, memory.deleted,
memory.retention_cleanup |
| No validity time separate from record time | git grep -n -i -E "valid_from|valid_to|as_of|observed_at|effective_" -- "src/schema/*.ts" "src/*schema*.ts" |
Nothing |
| No paper is cited | git grep -n -i -E "arxiv|bibtex|@article|@misc|doi\.org" -- README.md docs |
Nothing |
| The service is named only in the hook | git grep -n -i -E "living[-_ ]?mind|localhost:8008" |
src/hooks/system-transform-live-end.ts,
src/hooks/system-transform-live-types.ts and four index
rows in docs/SYSTEM_MAP.md |
The server's context route has no dominant_emotion in
hormones (in the living-mind-cortex checkout) |
git grep -n -E "dominant_emotion" -- api/agent_gateway.py |
agent_gateway.py:256 in the /state route,
:345 and :406 as local reads; none in the
hormones object at :378-384 |
| Tree and suite size | git ls-tree -r --name-only HEAD -- src | grep -c "\.ts$";
the same list piped through xargs cat | wc -l;
ls test/*.test.ts | wc -l;
cat test/*.test.ts | grep -c -E "\b(test|it)\(" |
414 files and 57,379 lines; 212 test files, 1,782 call sites |
History
2026-10-01 — 9c7cfb22…
— audited at the same commit; scope_enforced withdrawn. The
mark rested on the search path, which fails closed.
LessonTriggerCache.refresh reads the same
memories table with no project_id predicate
(src/lesson-trigger-cache.ts:74-80), and the
system-transform hook injects the result on every turn
(src/hooks/system-transform.ts:40), so an injection read
over the same store cannot carry the predicate. The search path is the
near-miss (section 9). The
zero-row retry to legacy is recorded beside it as a weaker
point. Two marks remain, audit_log and
negative_eval. Nothing installed, built or run.
2026-09-28 — 9c7cfb22…
— audit at an unchanged pin; master had not moved. The
headline criticism was wrong at this commit: since 09e42f98…
(17 September 2026) the shared WHERE builder and both fallbacks exclude
superseded and archived rows, with a populated test that becomes the
negative_eval record. The gap that remains is the
lesson-trigger cache, unscoped and unfiltered every turn (section 2). AgentBook declares 25 types, not
26, and eight of ten Recent Work entries are CSM's own tools, not seven
of nine. memory_events covers three channels, not every
mutation. Body anchors into the pre-split memory-manager.ts
and fourteen other drifted lines were corrected. Marks unchanged.
CSM_LIVING_MIND_URL is answered by route and field match
(section 8). Re-screened: three
auto-run surfaces, eleven floating ranges, AGENTS.md as
data; nothing installed, built or run.
2026-09-18 — 9c7cfb22…
— re-pinned from 4361d38; src/ and
test/ moved, migrations/,
plugins/ and hooks/ did not, and the tree was
re-screened at the new pin. The three marks hold and both surviving
anchors were stale: memory-manager.ts was split, with the
memory_events insert now at
memory-manager-base.ts:1057 after a 1,292-line extraction,
and appendProjectScope moved to
hybrid-search-sources.ts:39-57. The
scope_enforced record gains the qualifications a reader
needs: 1=0 is the fail-closed behaviour of
project mode, one of three — global appends no
clause at all and legacy widens to
project_id = $n OR project_id IS NULL — and
searchMemoriesOp moves between them by itself, retrying a
zero-row project search as legacy and passing
the widened mode into the cascade. The widening reaches unscoped rows
rather than another project's, which is why the mark stands; it was
there at the previous pin too, and this report did not say so. New here:
src/memory-provenance-audit.ts counts how many memories
carry all six required provenance fields, per field and separately over
active rows, and distinguishes the unknown-model sentinel from the
default — a store auditing the gaps in its own audit trail, printed by
scripts/csm-audit.ts.
2026-09-11 — 4361d38d…
— re-read, 189 files, 10,344 insertions and 4,568 deletions past the
previous pin, arriving in one commit. Both central criticisms
hold, and both are sharper. buildWhereClause is
the one WHERE builder all three search lanes share and its whole body is
still project, type, tags and importance — so supersession and archival
remain invisible to search while agent-onboarding.ts
filters archived_at IS NULL at two places, which is why the
same store answers differently depending on the door. The belief tier's
producer gap was described as three read paths; it is four, and the trap
has a name: src/belief-promotion.ts is 400 lines, is wired,
logs "Belief promotion: N promoted", and promotes
memory_candidate_queue rows into memories
without touching belief_knowledge_store. The
memory_candidates finding is restated precisely — no
INSERT exists anywhere in src/ against that
table, and it is a different table from the live
memory_candidate_queue, which two modules do fill.
One substantial addition:
lesson-auto-promotion.ts, a promoter gated on recall count,
distinct-session count, a content-quality heuristic and a content-hash
dedup, refusing named noise shapes by regex, tagging what it promotes
auto_promoted "so they can be audited and
reverted" — cross-session usefulness as the durability signal,
which is a better rule than a score assigned at write time. Marks
unchanged at three, each re-verified, and
capability_evidence records added where the report had
none. classifyValueClaim moved from
value-source-guard.ts:44 to :45; the search
and fallback line numbers still resolve. Suite 1,686 call sites across
189 files to 1,758 across 202, with the committed
full-test-output.txt unchanged at 808. Screened before
reading: three auto-run surfaces (.mcp.json,
hooks/, hooks/hooks.json), eleven floating
ranges behind a lockfile 31 days old, and an AGENTS.md read
as data; nothing was installed or run.
2026-07-30 — 21d00969…
— first reading.