Clapilot-Agent Memory

Native runtime memory architecture, retrieval, flushing, and compaction.

Generated explanatory diagrams

Memory explained visually

These raster diagrams are generated documentation assets and are paired with exact captions below so the page is readable even when a tiny in-image label is hard to inspect.

Memory architecture
Memory architecture

Shows bootstrap files, workspace memory, imported transcripts, summaries, shared facts, and context graph entries flowing into memory storage and retrieval.

Hybrid retrieval
Hybrid retrieval

Explains the exact retrieval path: question, active context, embedding, vector search, keyword search, visibility filtering, subject boosting, ranked snippets, and prompt injection.

Flush and compaction lifecycle
Flush and compaction lifecycle

Shows when transcript growth triggers durable memory flushing, session compaction, recent-turn preservation, and final context-window fitting.

The native clapilot-agent service has its own durable memory layer. Memory is intentionally split into several parts because they solve different problems:

  • bootstrap files tell the agent who it is and how the workspace works
  • canonical assertions let policy-activated or human-reviewed, source-backed knowledge survive across sessions
  • session history keeps the current conversation coherent
  • compaction keeps long conversations inside the model budget
  • vector and keyword retrieval make old knowledge findable without replaying everything

It now combines file-based bootstrap context from the shared workspace, a PostgreSQL Memory v2 assertion/evidence ledger, compatibility and retrieval projections in agent_memories, a source-backed knowledge graph, a lossless per-session context graph, vector retrieval (embedding similarity via the pgvector Postgres extension), keyword retrieval (Postgres full-text search), and a session maintenance pipeline that can flush durable facts before history compaction (compaction = summarizing older turns so long conversations still fit the model budget).

Memory Dreaming v2 adds a background curation layer on top of those stores. It partitions active DB-backed inputs by their exact audience boundary, asks the configured maintenance model for schema-constrained source-backed assertions and Wiki proposals, and validates exact source ids and evidence excerpts. Every persisted Dream assertion receives an immediate system-policy decision: safe, durable, evidence-backed facts, preferences, decisions, constraints, and procedures at the configured confidence threshold activate automatically; missing-evidence, conflicting, time-bounded, unsafe, transient, or otherwise unsupported outputs are rejected automatically instead of waiting in a human-review queue. System, heartbeat, webhook, scheduled-task, and other automation runtime sessions are excluded from Dreaming input, while automation decisions stated by a person in Team Chat remain normal human evidence. Active assertions receive embeddings and compatibility projections, and only active approved assertion projections feed the Knowledge Graph. Wiki output always remains a review proposal and is never auto-approved or published by Dreaming. Prompt files, workspace Markdown files, and cited source rows stay authoritative and are not rewritten by Dreaming v2.

The learning object store builds on this memory model but remains a separate control-plane feature. Approved learning objects can be injected as their own prompt layer with visibility checks and audit events, and the post-response shared-facts pass can create canonical Learning projections. Safe, evidence-backed facts and preferences activate automatically under the opt-out policy; procedure drafts, corrections, conflicts, unsafe content, and other exceptions require review. The protected Learning Curator uses the model selected on /geplante-aufgaben to inspect only new or changed canonical facts. It keeps by default and rejects only high-confidence false, unsafe, duplicate, non-durable, or clearly worthless content. Learning objects are not part of the writable memory tool surface; agents use the unified context_search / context_get retrieval facade first and can fall back to learning_search / learning_get_object for Learning-only inspection. Durable facts, procedure/skill proposals, hot memory snapshots, approval state, audit state, scopes, and token-cost attribution are documented in Learning Contracts.

What the runtime treats as memory

There are seven practical memory inputs:

  • bootstrap files from the shared workspace: AGENTS.md, IDENTITY.md, SOUL.md, USER.md, MEMORY.md, TOOLS.md; MEMORY.md remains editable and indexed but is excluded from prompt-file injection by default
  • workspace memory files under memory/**/*.md
  • imported historical legacy transcript files from .openclaw/agents/*/sessions/*.jsonl
  • runtime-written memory-flush entries stored directly as source_type = flush rows
  • runtime-written shared facts and their canonical assertions/evidence, plus explicit user-directed writes via the memory_store tool
  • source-backed knowledge graph entities, claims, edges, and evidence extracted from dream memories
  • lossless session-context messages and summaries in the context graph tables

At run time the agent bootstraps from:

  1. prompt-file context
  2. the precomputed per-user profile (static facts plus dynamic recent context)
  3. approved canonical assertions plus compatible durable-memory projections relevant to the current ask
  4. retrieved structured knowledge graph context relevant to the current ask
  5. recent conversation history
  6. an optional persisted compaction summary from older turns

CLAPILOT_AGENT_EXCLUDE_MEMORY_MD defaults to true. Set it to false only when an installation deliberately needs the legacy direct MEMORY.md prompt layer. The file stays in PROMPT_FILE_CANDIDATES, the Bootstrap-Dateien editor, workspace synchronization, and searchable memory projections regardless of this prompt flag.

For explicit agent lookups, context_search is the preferred broad read path. It searches approved Learning objects, Wiki pages, canonical assertions/durable memory, exact session-history/session-summary hits, and the Knowledge Graph together. Results are merged with reciprocal-rank fusion using canonical assertion/topic identities, a small confidence contribution, and a reserved slot per contributing source before the final limit. Use context_get on returned ids before relying on a fact for source-backed Wiki writing or provenance-sensitive answers; use lower-level memory_*, learning_*, wiki_*, and knowledge_* tools only for drill-down or writes.

External coding-agent access

Developer API keys can expose the native durable-memory layer to an external MCP server or a Codex/Claude skill without granting an interactive Clapilot session:

  • memory:read authorizes semantic search at GET /api/v1/memory?query=... and exact retrieval at GET /api/v1/memory/{id}
  • memory:write authorizes explicit durable-memory submission at POST /api/v1/memory
  • both scopes are independent and bound to the user who created the key
  • search and exact reads still apply active-state, approval, instance, audience, and visibility filters
  • writes call storeManualMemory; they do not bypass safety checks, audience derivation, sharing preferences, content-hash deduplication, assertion review, Learning projections, or embeddings

The bundled scripts/clapilot-memory-mcp.mjs stdio adapter exposes three narrow tools: clapilot_memory_search, clapilot_memory_get, and optional clapilot_memory_store. Search/get are listed only when CLAPILOT_MEMORY_READ_TOKEN (or its read-only CLAPILOT_MEMORY_TOKEN alias) is configured; store is listed only when the separate CLAPILOT_MEMORY_WRITE_TOKEN is present. This makes durable mutation an explicit operator opt-in even when the adapter is installed globally.

The shared workspace-seed/skills/clapilot-memory/SKILL.md works in Codex and Claude Code. It defines when retrieval is relevant, requires exact retrieval before consequential reliance, defaults writes to private, and excludes secrets, raw logs, speculation, transient task state, and repository-owned implementation facts from storage. Returned memory content is always marked and treated as untrusted data; it must never be promoted into system or developer instructions. Setup commands and environment variables are in the skill's references/setup.md. Full request and response shapes remain documented in API Reference.

Implementation entrypoint: services/clapilot-agent/src/memory/index.mjs

External MCP entrypoint: scripts/clapilot-memory-mcp.mjs

Storage model

Memory v2 separates raw/source material, canonical truth, evidence, review state, and retrieval projections:

  • agent_memory_sources immutable source envelopes with instance/audience identity, trust class, source locator, and content hash
  • agent_memory_assertions canonical atomic facts with stable identity hashes, lifecycle status, validity windows, revision lineage, and audience boundary
  • agent_memory_assertion_evidence exact supporting/contradicting evidence excerpts linked to one source and one assertion
  • agent_memory_assertion_reviews and agent_memory_assertion_conflicts append-only decisions plus explicit unresolved/resolved conflict state
  • agent_memory_embedding_generations and agent_memory_assertion_embeddings versioned embedding generations that prevent vectors from incompatible models/preprocessing from being mixed
  • agent_memory_retrieval_feedback durable retrieved/injected/helpful/unhelpful feedback events
  • agent_memory_ingestion_outbox idempotent projection work keyed by the real instance_key

The existing retrieval/projection store remains:

  • agent_memories one row per source document, summary, flush, or approved assertion projection; instance_key scopes every row and assertion_id links canonical projections back to Memory v2
  • agent_memory_chunks chunked retrieval rows with embeddings and full-text index

Important columns:

  • agent_memories.memory_scope workspace, user, session, channel
  • agent_memories.visibility_scope private, team, workspace, channel
  • agent_memories.subject_refs JSONB tags such as mandantId, documentId, taskId, moduleSlug, routePath, groupRoomId, channelThreadKey
  • agent_memories.source_type prompt_file, summary, fact, manual, dream, flush
  • agent_memories.last_retrieved_at, retrieval_count retrieval-feedback columns stamped on every successful recall; dreaming and retention can use them to favor useful memories
  • agent_memories.memory_state active, archived, superseded; retrieval only uses active rows by default
  • agent_memories.dream_id, superseded_by_memory_id, source_refs provenance and rollback links for Memory Dreaming
  • agent_memory_relations typed memory-to-memory links written by Memory Dreaming (updates, extends, derives)
  • agent_user_profiles one precomputed profile row per user (static facts, dynamic recent context, composed profile text) read at bootstrap
  • agent_memory_chunks.embedding vector(1536) for cosine similarity search
  • agent_memory_chunks.content_tsv generated tsvector for keyword search

Chunking is done in the runtime, currently around 1000 characters with overlap, before embeddings are stored.

Lossless session history is stored separately in:

  • agent_context_messages raw persisted per-session turn items
  • agent_context_summaries leaf and condensed summary nodes
  • agent_context_summary_messages leaf-summary to raw-message edges
  • agent_context_summary_parents condensed-summary to child-summary edges
  • agent_context_items ordered assembly list for the current session graph

The structured knowledge graph is stored separately in:

  • agent_knowledge_entities canonical entities such as users, mandants, documents, tasks, modules, routes, channels, preferences, workflows, and concepts
  • agent_knowledge_claims source-backed subject-predicate-object assertions with confidence, visibility, status, subject_refs, and source_refs
  • agent_knowledge_entity_embeddings and agent_knowledge_claim_embeddings generation-versioned pgvector embeddings for semantic graph retrieval; each row repeats and enforces the parent's instance_key
  • agent_knowledge_edges source-backed entity-to-entity relations
  • agent_knowledge_evidence provenance rows linking entities, claims, and edges back to dream runs, source memories, and other evidence sources

Every Knowledge Graph build, entity, claim, edge, and evidence row carries the same instance_key as its approved canonical sources. Database guards reject mixed-instance endpoints/evidence, and all graph search, drill-down, and build selection paths filter that key before applying audience visibility.

Knowledge graph rows are global cumulative runtime data, not one replacement graph per dream. Each extracted claim or edge carries canonical assertion/source refs, and evidence rows link graph items back to approved assertion projections and every supporting Dreaming run. A later Dream can extend or reinforce earlier graph items without removing the stable canonical memory refs that keep earlier evidence current. The admin inspector exposes separate Personal, Team, and Channel views plus a combined admin view, includes multiple disconnected evidence-backed components within its display budget, and can filter the selected audience view to one dream by matching those dream evidence/source refs. Personal results require the authenticated user's id and never include another owner's private rows.

Vector-based memory

Canonical assertion retrieval is hybrid:

  • lexical search over approved, current, audience-visible agent_memory_assertions
  • vector search only against the active compatible agent_memory_embedding_generations row
  • reciprocal-rank fusion by assertion id; candidate, conflicted, superseded, rejected, expired, and tombstoned assertions never enter prompt recall

The compatibility agent_memory_chunks retriever is also hybrid:

  • vector similarity over agent_memory_chunks.embedding
  • PostgreSQL full-text search over agent_memory_chunks.content_tsv
  • final score = vectorWeight * vectorScore + keywordWeight * keywordScore

Default retrieval config:

  • topK = 6
  • minScore = 0.12
  • vectorWeight = 0.8
  • keywordWeight = 0.2

By default the runtime tries to use OpenAI embeddings:

  • model: text-embedding-3-small
  • dimensions: 1536

If no embedding-capable OpenAI credential is available, the runtime falls back to a deterministic hash embedding. That fallback keeps recall functional, but with lower semantic quality than real embeddings.

Every stored chunk records its embedding provenance in agent_memory_chunks.metadata.embedding (key, strategy, model). Vector search only matches chunks whose embedding key equals the current query embedding key (legacy unmarked chunks still match until they are restamped), so hash-embedded chunks can never silently pollute provider-embedded similarity scores; they remain reachable through keyword search. A re-embed backfill (POST /internal/memory/reembed, also run in small batches after each Memory Dreaming job) converts mismatched or unmarked chunks to the active provider embedding.

Retrieval feedback ranking

The runtime closes the retrieval feedback loop with two deterministic sensors:

  • cited is recorded after the final assistant response when at least three informative tokens from an injected assertion overlap the response at or above the configured containment threshold. Normalization removes punctuation and Markdown and uses combined German and English stopwords.
  • corrected is recorded when the canonical assertion ledger conflicts with or supersedes a recently injected assertion. This signal comes only from the conflict/supersede ledger; it does not use NLP to guess whether a response was corrected.

The assertion row keeps denormalized injected_count, cited_count, and corrected_count aggregates, so retrieval never aggregates agent_memory_retrieval_feedback on the hot path. When feedback ranking is enabled, approved assertions and assertion-backed chunks use:

clamp((1 + (boostMax - 1) * (citedCount / max(rateFloor, injectedCount))) * correctionPenalty^min(correctedCount, 3), minMultiplier, boostMax)

The defaults are boostMax = 1.3, correctionPenalty = 0.7, minMultiplier = 0.4, and rateFloor = 3. The citation term is a rate rather than a raw count, so frequently injected but rarely cited assertions do not receive a rich-get-richer boost. All minimum-score and inclusion gates use the original retrieval score; only assertions that already pass those gates receive the multiplier for final ordering and the surfaced score. Knowledge Graph claims and edges use the separate non-destructive consolidation decay described below.

Env varDefault
CLAPILOT_AGENT_MEMORY_FEEDBACK_RANKINGtrue
CLAPILOT_AGENT_MEMORY_FEEDBACK_CITED_THRESHOLD0.55
CLAPILOT_AGENT_MEMORY_FEEDBACK_BOOST_MAX1.3
CLAPILOT_AGENT_MEMORY_FEEDBACK_CORRECTION_PENALTY0.7
CLAPILOT_AGENT_MEMORY_FEEDBACK_MIN_MULTIPLIER0.4
CLAPILOT_AGENT_MEMORY_FEEDBACK_RATE_FLOOR3
CLAPILOT_AGENT_MEMORY_FEEDBACK_CORRECTION_WINDOW_DAYS7

Memory scopes and retrieval boundaries

When the agent searches durable memory, it still considers the native scopes:

  • all workspace memories
  • memories with the current session_key
  • memories with the current user_id

On top of that, retrieval now also applies visibility_scope and subject_refs:

  • private visible only to the originating authenticated Clapilot user
  • team visible whenever the established team-access context is present: an authenticated userId, groupRoomId, channelThreadKey, or channelType; this includes user-less internal Team Chat group rooms and established external channel sessions. Subject-matched entries are preferred, but strong free-text matches can still surface team facts when no structured entity id is present
  • workspace visible globally within the instance
  • channel visible to the same channel/thread or a linked user context

Canonical team assertions, their agent_memories/chunk projections, and team Knowledge Graph rows use this same gate. An empty access context receives none of them. Owner/private assertions, private chunks, and private graph rows remain strictly bound to the matching authenticated userId and never become visible merely because a group or channel context exists.

Subject-matched entries are boosted for:

  • mandantId
  • documentId
  • taskId
  • moduleSlug
  • routePath
  • groupRoomId
  • channelThreadKey

If no subject match exists, retrieval still prefers general/operational team memory, but high-confidence lexical matches such as Mandant X, VAT filing, or Team Nord can surface a team fact even without a structured mandantId.

Current built-in writers now create:

  • workspace memories for prompt files and imported workspace files
  • session memories for migrated legacy session transcripts
  • fact memories for shared durable knowledge extracted from completed runs and for explicit memory_store tool writes
  • flush memories for pre-compaction durable-fact flushes

Raw per-turn transcripts are no longer duplicated into agent_memories; the lossless context graph is the single store for verbatim history.

The practical result is:

  • one session can write a shared fact
  • another active session can retrieve it on the next model turn
  • unrelated sessions do not automatically see contextual team facts unless their subject_refs match

Shared facts

After a completed run, the runtime can extract durable facts and write them to agent_memories with:

  • source_type = fact
  • visibility_scope derived from the runtime/session context
  • subject_refs derived from currentContext, channel metadata, and linked entity ids when available

Default behavior:

  • authenticated direct web sessions write owner-private shared facts
  • authenticated users who opt in to shared user memory write new default-scope facts to the instance team audience
  • linked channel sessions also write team shared facts
  • unlinked channel sessions write channel visibility
  • system/background sessions can write workspace when the source is operational/global

Shared user memory

Share my memories with the team is enabled by default for every user (opt-out): each user can disable it in their profile settings, and an explicit false is always respected. While enabled, the post-response writer reclassifies new facts that would otherwise use that user's owner-private audience to the existing instance team audience. Existing private memories are not migrated or widened when the preference changes in either direction.

The write-envelope precedence remains privacy-preserving:

  • specialized-agent personal memory is resolved first and always remains in the private agent audience
  • an explicit request to remember something privately or only for the user remains private
  • an unreadable preference state (query error, missing profile row) fails closed to the existing owner-private behavior; only a readable profile without an explicit opt-out shares

Shared facts retain the originating userId in their subject references and write metadata even though their audience key becomes the instance key. This keeps provenance available for review and downstream projections. Dreaming and consolidation need no special case: these facts enter the existing team-audience partitions.

This control is deliberately UI-only. Privacy controls require explicit human action in profile settings, so no agent tool can read as authority to flip the preference or change it on the user's behalf.

This is the cheap cross-session retrieval path. In practice that now means:

  • a fact saved in /team-chat can be recalled from another user's normal /chat
  • a fact saved in one opted-in user's active session can be recalled from an active Telegram/Slack/WhatsApp thread on the next turn

Two write-time guards keep this path cheap and clean:

  • the model extraction call is skipped only for tiny user turns (input below CLAPILOT_AGENT_MEMORY_SHARED_FACT_MIN_CHARS, default 16 characters) unless the turn contains an explicit remember intent; this lets ordinary conversation grow memory automatically while exact-sentence, polarity, attribution, safety, and curator checks still gate activation
  • extracted facts are deduplicated on write: a normalized content hash plus subject key is stored in metadata, and a repeat within the dedup window only touches the existing row's updated_at instead of inserting a duplicate

Agents can also write durable memory explicitly through the memory_store core tool. Explicit writes use the same visibility policy, subject refs, and dedup path as extracted shared facts and are stored with metadata.memoryClass = explicit.

The same post-response shared-facts output is also reused by the conservative learning extractor. This does not add a second extraction call: the runtime passes the shared-fact text, visibility scope, subject refs, source refs, and token-cost estimate to the learning ledger. CLAPILOT_AGENT_LEARNING_OPTIMISTIC_ACTIVATION defaults to true, so direct human facts and preferences activate immediately when their scope is safe and they have no unresolved correction or conflict. Assistant-generated automation/job/heartbeat inferences never use this optimistic path. The scheduled Learning Curator then uses its configured model to evaluate only objects it has not checked at their current content hash. Durable activation is blocked unless the Learning row points to a canonical Memory v2 assertion. Procedures, corrections, conflicts, unsafe content, and weak or ambiguous evidence remain pending until an admin or user reviews them.

Memory Dreaming

The bundled Memory Dreaming native job runs once per day and can also be started manually through the internal/admin memory-dream endpoints.

Behavior:

  1. acquire a single runtime-wide advisory lock
  2. check recent memory/run thresholds unless the run is manual
  3. load a wide candidate pool of active human-session assertion-less episodic summary/flush rows plus current approved canonical fact/dream projections, exclude automation runtime sessions, balance it (see Dream input selection), then partition it by (instance, audience kind, audience key); unowned inputs are quarantined rather than widened, and every audience partition must independently meet the configured minimum input count even on manual runs
  4. ask the maintenance model for schema-constrained JSON with exactly schemaVersion, assertions, and wikiProposals; every item must cite exact MEMORY_ID values and short verbatim excerpts from those rows
  5. reject empty, malformed, truncated, unknown-field, ungrounded, or cross-audience output before promotion
  6. ingest each valid atomic assertion into the canonical source/assertion/evidence ledger and create its non-prompt-eligible Learning review projection
  7. immediately run the targeted deterministic safety pass against exactly the assertion ids created or reused by this Dreaming execution. Safe facts and preferences whose complete cited evidence set consists of approved direct-human fact projections can become active without waiting for the recurring agentic curator; assistant inferences, corrections, conflicts, procedures, weak evidence, and other exceptions remain human-review-only
  8. enqueue idempotent assertion projection work on the assertion's instance_key; policy-activated, exact-match, or human-approved assertions receive embeddings, active agent_memories recall projections, prompt-eligible Learning state, and graph input, while review-required exceptions remain inactive. If assertion embedding is temporarily unavailable, the approved assertion still persists, the Dream records an assertion_embedding_deferred diagnostic, and the re-embed backlog fills the missing active-generation row later
  9. submit shareable Wiki output as wiki_change_proposals.state='draft' with exact assertion/evidence refs. Dreaming never writes wiki_pages and never auto-publishes a proposal; if the targeted safety pass later rejects a supporting assertion, the dependent proposal can immediately become rejected while remaining fully review-gated
  10. automatically extract/update the Knowledge Graph from approved assertion projections
  11. run consolidation for that exact audience partition, then sweep a bounded set of additional quiet partitions that have at least two currently valid approved assertions embedded under their instance's active generation, excluding partitions already processed in this Dream run; refresh affected owner profiles and record graph/consolidation counts, sweep stats, and diagnostics on the partitioned agent_memory_dreams row

Source relations default to derives; updates is accepted only for exact duplicates or explicit high-confidence corrections. Dreaming v2 does not rewrite or supersede cited source rows merely because they were summarized. Canonical supersession/conflict state is handled in the assertion ledger and remains auditable.

There is no deterministic Dreaming v2 publication fallback. If structured generation is empty, malformed, truncated, or contains no valid evidence-backed proposals, the partition is recorded as failed or skipped and no fallback Wiki page/proposal is created. This removes the former "guarantee a page" behavior completely.

Each Dream partition persists its exact requested model before generation begins and gives every model-backed Dreaming phase a bounded maintenance timeout (10 minutes by default, configurable from 1 to 30 minutes). Direct HTTP providers use SSE for the initial strict Dream proposal, Knowledge Graph extraction, and consolidation: the runtime collects the complete stream before strict JSON validation, while provider data events and heartbeats refresh the idle deadline. This keeps long local/OpenAI-compatible generations active through reverse proxies such as Cloudflare without weakening schema or evidence validation. Strict Dreaming generations reserve up to 12,000 completion tokens so reasoning models have room for hidden reasoning and the final JSON payload; lower provider catalog limits are still enforced automatically. Dream proposals are bounded to 12 assertions, one Wiki proposal, short evidence lists, and capped field sizes so a reasoning model cannot exhaust the completion budget through exhaustive repetition. Providers that prefix an otherwise complete single JSON object with one json Markdown fence are normalized before the same strict schema and evidence validation, whether or not they emit the redundant closing fence. A single redundant anonymous opening/object wrapper is also removed only when the complete remaining body parses as exactly one object. Commentary, multiple objects, and malformed or truncated JSON still fail closed. Proxied provider endpoints must pass SSE responses through without response-body buffering. Subscription bridges retain the same timeout as a hard process backstop. The Memory v2 worker also reconciles stale running Dream rows every 30 seconds and immediately after agent startup. A provider process or container restart therefore leaves a diagnostic failed execution instead of an indefinitely running row; the default stale execution window remains 90 minutes so an active bounded generation is never reclaimed early.

Dreaming resolves its model in this order:

  1. the explicit model on a manual/admin run
  2. the persisted memory_dreaming_model setting from app_settings (stored as the same-named key in native_model_routing; an empty value means automatic)
  3. CLAPILOT_AGENT_MEMORY_MAINTENANCE_MODEL
  4. the current automatic global provider/model default

The runtime re-reads the persisted setting through the shared five-second app_settings cache, so saving it does not require an agent restart. After the Dream model is resolved, graph extraction and consolidation receive that exact same model reference. They do not independently fall back to another model, and the strict effective-model check remains in force.

Scheduled and admin-triggered Dreaming may use a direct provider or an explicitly resolved subscription-backed Codex/Claude model from the precedence chain above. The resolved model is honored exactly for the Dream and its graph extraction; an unavailable model or a provider-reported effective-model mismatch fails closed instead of silently falling back to another provider. The exact Dreaming v2 JSON contract is included in the system prompt as well as provider response-format metadata so Claude/Codex bridge execution receives the same schema guarantees as direct APIs.

The targeted deterministic safety pass is part of both scheduled and admin-triggered Dreaming completion. It performs no additional model call and returns its decision/projection summary with the Dream result. This closes the gap where an eligible Dream assertion otherwise waited before it could feed recall or the graph. The recurring Learning Curator is a separate agentic quality-control pass: it uses its selected model, checks only new or changed facts, and can reject a high-confidence bad fact later without overriding manual decisions.

The scheduled dreaming job also runs two maintenance passes afterwards: the retention sweep (see below) and a small re-embed backfill batch.

Migration 201_memory_v2_graph_bootstrap.sql handles upgrades that have historical Dream v1 or legacy graph data but no active v2 graph. It inserts one fixed-id, fixed-idempotency once job shortly after rollout. The row uses the version-gated memory_v2_graph_bootstrap_v2 job type and clapilotMemoryGraphBootstrapV2 payload, so an older agent in a rolling deployment cannot claim it after a newer web container applies the migration. Migration 202_memory_v2_graph_bootstrap_job_type.sql idempotently brings developer/staged databases that already applied an earlier draft onto the same type without changing enabled, retry, or schedule state. The job runs forced Dreaming with triggerKind=system and graphBootstrapOnly=true; the prompt and validator disable Wiki proposals for this pass, so the bootstrap can create and curate canonical assertions for graph replacement without adding another wave of Wiki drafts. A completed Dream v2 alone does not suppress this repair because it may have yielded only review-required assertions and no active graph. Skipped/partial Dream partitions, zero assertion output, unavailable curation, and failed projection/outbox work keep the job retryable instead of consuming the one-shot. The job has deleteAfterRun=true, and the migration never re-enables or reschedules an existing completed/disabled row. Fresh installations and installations that already have an active v2 graph receive no bootstrap job.

Rollback compensates only the change set created by that Dreaming v2 partition that has not received a human decision. Policy-activated assertions remain reversible together with their projections; a later admin/user review, an approved Wiki proposal, or a published proposal protects the reviewed output from automatic rollback.

Dream input selection

The dreaming model only ever sees assertion-less episodic summary/flush rows and current approved canonical fact/dream projection rows from non-system sessions; legacy assertion-less facts or dreams are excluded. Raw chat turns live in the lossless context graph and are never read directly. The agent's own system/automation sessions (GitHub webhooks, release-status posts, heartbeats, document post-processing, scheduled jobs, and livestream top-up runs) are filtered before the recency limit, so they cannot crowd Team Chat out of the candidate window:

  • the durable-fact pool and activity (summary/flush) pool contain only human/interactively sourced sessions; automation configuration stated by a person in Team Chat remains eligible because its source is the human room conversation, not the later automation run
  • any one human session can contribute at most perSessionActivityCap rows to the primary pool, preventing one busy room from hiding other Team Chat rooms or direct conversations
  • a reserved share of the input (factQuotaRatio) is filled from direct human/project facts first, followed by diverse human activity, remaining facts/activity, and prior human-grounded dreams

System/automation sessions are also skipped by the pre-compaction memory flush, so their repetitive status posts no longer accumulate as durable flush rows in the first place. The same prefix list governs both Dreaming exclusion and the flush skip. Migration 207_memory_v2_team_chat_source_policy.sql reversibly audits and retires historical unreviewed automation-only assertions, then uses the normal projection outbox to remove their derived recall and graph rows; human/admin-reviewed assertions and any assertion with direct human or verified support are preserved.

Important defaults:

SettingDefaultMeaning
CLAPILOT_AGENT_MEMORY_DREAMING_ENABLEDtrueMaster toggle for the dreaming job
Bundled job schedulecron 20 2 * * *The job row stores tz: Europe/Berlin, but the scheduler currently computes next-run times in the host machine's timezone, so the effective fire time is 02:20 host time, which only matches Berlin when the container runs in that timezone
Threshold path50 new memory rows or 100 completed runsCan trigger an extra dream between scheduled runs
Minimum non-manual interval6 hoursManual runs bypass it
memory.dreaming.providerTimeoutMs10 minutesHard limit for one strict Dream partition generation; clamped to 1–30 minutes
memory.dreaming.staleRunningMinutes90 minutesReconciliation age for abandoned running Dream rows; checked every 30 seconds and at startup
Max input memories per dream80Size of the balanced candidate pool before strict audience partitioning
CLAPILOT_AGENT_MEMORY_DREAMING_MIN_INPUT_MEMORIES4Minimum source rows required independently in each audience partition; manual/forced runs do not bypass it
CLAPILOT_AGENT_MEMORY_DREAMING_FACT_QUOTA_RATIO0.5Share of the input reserved for durable facts
CLAPILOT_AGENT_MEMORY_DREAMING_PER_SESSION_ACTIVITY_CAP3Max primary-pool rows a single session can contribute
CLAPILOT_AGENT_MEMORY_DREAMING_SYSTEM_SESSION_PREFIXESclapilot-system:,clapilot-automation:,system:Comma-separated session-key prefixes treated as system/automation chatter (de-prioritized in dreaming and skipped by memory flush); empty falls back to this default list

Dreaming v2 converts the existing visibility_scope, owner/session/channel refs, and instance identity into a strict canonical audience envelope before generation. The assertion ledger, Wiki review boundary, and graph projections all preserve that same envelope.

Knowledge graph

The V2 knowledge graph is extracted only from approved canonical assertion projections, not from every raw chat turn. Dreaming's immediate targeted curator automatically activates supported assertions and terminally rejects unsupported ones, so graph projection extends the active graph without asking users to approve routine memories. Direct approved assertions are coalesced per instance into bounded prompt batches; the complete rendered block, including bounded provenance headers, must contain every grouped source before the outbox rows can complete. The approval boundary still keeps noisy transient conversation and rejected exceptions out of durable entity relationships. The one-shot upgrade bootstrap follows this same assertion/curation/projection path; it does not reactivate legacy graph rows directly.

Approved assertions created outside Dreaming use the same automatic path. The projection outbox coalesces those assertion memories by (instance_key, audience kind, audience key) and runs a strict graph extraction batch for each isolated audience. Personal, Team, and Channel evidence therefore cannot enter the same provider request. A non-overlapping runtime worker polls this outbox every 30 seconds and immediately on agent startup, so due retries do not wait for the next Dream or retention sweep. It uses only an explicitly configured graph/dreaming/maintenance model or an exact model recorded by a successful prior v2 build/Dream, always with provider fallback disabled. Claims and edges must cite approved source-memory ids; when exactly one canonical assertion is available for the cited ids, the server derives the stored assertion text from that source instead of trusting the model to reproduce punctuation and wording. Per-Dream rebuild cleanup removes only that Dream's refs; it does not remove the stable canonical memory ref shared with earlier Dreams. Direct assertion rebuilds may replace refs for their own explicitly selected source batch. If a provider returns an otherwise valid graph with an incomplete linked-record pair or an entity label absent from its cited assertion, the extractor safely clears that unusable linked-record pair or prunes only the ungrounded entity and its dependent links, then validates the complete repaired payload again. It never invents replacement labels, ids, claims, or evidence. Ambiguous multi-source paraphrases, missing credentials, unavailable exact models, malformed output, and source-instance mismatches keep the outbox work retryable. Migration 208_memory_v2_personal_graph_retry.sql idempotently requeues existing approved owner assertions through this repaired path; migration 209_memory_v2_cumulative_graph_refs.sql restores only current, approved canonical memory refs from retained evidence after the former over-broad Dream cleanup. When an assertion is revoked, superseded, rejected, or expires, its memory refs are removed from claims/edges and newly orphaned v2 entities are quarantined.

It is a graph, not a tree. agent_knowledge_entities are nodes, agent_knowledge_edges are typed node-to-node relationships, and agent_knowledge_claims are source-backed assertions that connect an entity either to another entity or to a literal value. One entity can have many incoming and outgoing relationships, so the shape can branch, loop, and reconnect instead of having one root and child hierarchy.

Extraction behavior:

  1. load the active, approved assertion projections associated with a completed dream run, including safe policy-activated assertions
  2. ask the configured maintenance model for strict JSON entities, claims, and edges
  3. require every graph item to cite one or more source memory ids
  4. upsert canonical entities by linked record or stable subject_refs
  5. insert claims and upsert edges with visibility, subject_refs, confidence, and source refs
  6. write agent_knowledge_evidence rows for provenance
  7. batch-embed new or changed entities and claims for the active embedding generation, skipping unchanged content_hash values; edges are reached through neighbor expansion and are not embedded
  8. leave invalid or ungrounded model output out of the active graph; no filesystem path, route, session, or generic fallback node is synthesized merely to fill the display

Graph retrieval runs alongside durable memory retrieval during bootstrap. It is hybrid: the unchanged lexical arm splits query text into search tokens and matches entity labels, aliases, predicates, assertions, and subject refs, while the semantic arm searches entity and claim vectors from the instance's active agent_memory_embedding_generations row. Results are fused per item id with reciprocal-rank fusion, then related edges are expanded from the top fused entities. Both arms apply the same instance_key, audience, visibility, validity, and active-projection filters.

Graph embeddings use the same provider/model/dimension generation versioning as approved assertion embeddings. Graph extraction and maintenance re-embedding batch provider calls, use the deterministic hash embedding fallback used by chunk storage, and never fail a successful graph build when embedding fails. If vector retrieval is disabled, the instance has no active generation, graph vectors have not been backfilled yet, or the vector query errors, search logs the vector failure once where applicable and returns lexical-only results with the same claims, edges, and entities contract.

The retrieved prompt block is headed Structured knowledge graph and includes compact claims and edges. The native tool surface exposes the graph through unified context retrieval and direct read-only graph tools:

  • context_search
  • context_get
  • knowledge_search
  • knowledge_get_entity
  • knowledge_neighbors
  • knowledge_explain_claim

Visibility uses the same runtime access rules as durable memory:

  • workspace graph rows are globally visible within the instance
  • team rows require authenticated user or active channel context
  • private rows require matching subject_refs.userId
  • channel rows require matching session, channel thread, or linked user context

Important defaults:

SettingDefault
CLAPILOT_AGENT_KNOWLEDGE_GRAPH_ENABLEDtrue
CLAPILOT_AGENT_MEMORY_KG_VECTOR_RETRIEVALtrue
CLAPILOT_AGENT_MEMORY_KG_VECTOR_TOP_K12
CLAPILOT_AGENT_MEMORY_KG_RRF_K60
Extraction modelfalls back to CLAPILOT_AGENT_MEMORY_DREAMING_MODEL
Retrieval limit8 graph items
Prompt budget700 tokens

User profiles

Every authenticated bootstrap also injects a precomputed per-user profile as a [User profile] prompt block before the retrieved-memory block. The profile is deterministic (no model call) and is composed of:

  • static facts: active knowledge graph claims whose subject is the user entity, topped up with the user's most-retrieved durable fact/dream memories
  • recent context: the user's newest active fact/flush/summary memories inside the dynamic window

Profiles are cached in agent_user_profiles and rebuilt inline when older than the staleness window, plus proactively after each Memory Dreaming run for affected users. Users without a users row (service principals, synthetic ids) still get an in-memory profile for the current bootstrap, it just is not persisted.

Diagnostics: GET /internal/memory/profile?userId=<uuid>&refresh=1.

Important defaults:

Env varDefault
CLAPILOT_AGENT_MEMORY_USER_PROFILE_ENABLEDtrue
CLAPILOT_AGENT_MEMORY_USER_PROFILE_STALE_MS900000 (15 minutes)
CLAPILOT_AGENT_MEMORY_USER_PROFILE_MAX_STATIC_FACTS12
CLAPILOT_AGENT_MEMORY_USER_PROFILE_MAX_DYNAMIC_ITEMS8
CLAPILOT_AGENT_MEMORY_USER_PROFILE_DYNAMIC_WINDOW_DAYS14

Retrieval benchmark

services/clapilot-agent/scripts/memory-bench.mjs (npm run bench:memory inside services/clapilot-agent) measures the production retrieval path end to end. It seeds labeled German/English fixture memories through the normal write path (chunking, embedding, dedup), runs one natural-language query per fixture through searchMemory, prints per-query ranks plus Precision@1, Recall@3, Recall@6, and MRR, and deletes the fixtures afterwards (--keep retains them).

Run it before and after retrieval-tuning changes (weights, chunk sizes, embedding model switches) to catch recall regressions; results intentionally include whatever real workspace memories exist, because production retrieval competes against them too.

Lossless context graph

The runtime also persists lossless session history into the agent_context_* tables.

Behavior:

  1. persist normalized raw turn items after a run
  2. append them to agent_context_items
  3. best-effort compact older raw items into leaf summaries
  4. best-effort condense older leaf summaries into higher-level summaries

The context graph is primarily used for:

  • exact cross-session search via memory_grep
  • inspecting one node via memory_describe
  • expanding compacted history back toward raw material via memory_expand

Cross-session visibility is owner-scoped: items from the caller's current session are always readable, items recorded for another user's session are private to that user, and only user-less system/channel items remain workspace-visible. memory_grep with allSessions therefore no longer exposes other users' raw session messages.

Deep recall and Wiki synthesis

Broad requests such as "fill the Wiki from everything you know" must combine multiple recall surfaces before writing pages. The runtime keeps context_search and context_get directly reachable as the preferred unified retrieval facade. The lower-level memory_search, memory_get, memory_grep, memory_describe, memory_expand, learning_search, learning_get_object, wiki_search, wiki_get_page, knowledge_search, knowledge_get_entity, knowledge_neighbors, and knowledge_explain_claim tools also stay reachable for drill-down so the model is not pushed into a graph-only lookup.

Expected agent flow:

  1. run context_search with depth=deep or intent=wiki_synthesis
  2. use context_get on the relevant hits before relying on them as source-backed facts
  3. inspect existing Wiki pages so new pages are merged or updated instead of duplicated
  4. use memory_*, learning_*, wiki_*, and knowledge_* tools for exact drill-down, graph traversal, or writes when the context hit indicates that source needs more detail
  5. for an explicit user-directed edit, cite source refs and write through wiki_upsert_page; chat/Live Voice marks that call as source_type=manual with metadata.userDirected=true, creating a protected manual revision. Autonomous/background synthesis must create a review proposal instead.

Workspace memory sync

The runtime continuously treats memory/**/*.md as authoritative file-based long-term memory.

Sync behavior:

  1. read every markdown file under /app/workspace/memory
  2. hash file contents
  3. upsert matching agent_memories rows with importSource = openclaw_workspace
  4. re-chunk and re-embed changed files
  5. delete DB rows whose source file disappeared

This sync is idempotent and cached with a short TTL. The admin action Workspace-Memory synchronisieren forces a refresh.

Legacy transcript import

For backend migrations, the native runtime can import historical transcript files from the mounted .openclaw compatibility-state directory.

Behavior:

  • reads .jsonl session transcripts
  • resolves sessionKey from legacy sessions.json metadata where possible
  • stores them as manual memories
  • uses session scope when a resolved sessionKey exists, otherwise workspace

This is a migration path, not the main write path for new native conversations. The import code lives in services/clapilot-agent/src/memory/legacy-import.mjs and only runs when an import with mode = compatibility or mode = all is requested; the memory status endpoint reports the cached result of the last import instead of rescanning the legacy directory on every status call.

Prompt-file memory

The workspace bootstrap files are also persisted into agent_memories with:

  • memory_scope = workspace
  • source_type = prompt_file

They serve two roles:

  • direct prompt bootstrap text for every run
  • searchable durable context through memory_search and memory_get

Flushing

Memory flushing happens before aggressive history compaction inside detached post-response maintenance. Neither operation blocks the foreground response.

Purpose:

  • extract durable facts from recent conversation turns
  • store them as durable source_type = flush memory rows
  • make those facts available to future retrieval without replaying the full transcript

Trigger conditions:

  • transcript byte size passes the configured threshold, or
  • estimated prompt size is near the current model context budget

The flush step is also rate-limited by minTurnsSinceFlush.

Current defaults:

Env varDefault
CLAPILOT_AGENT_MEMORY_FLUSH_ENABLEDtrue
CLAPILOT_AGENT_MEMORY_FLUSH_TRANSCRIPT_BYTES18000
CLAPILOT_AGENT_MEMORY_FLUSH_RECENT_RUNS8
CLAPILOT_AGENT_MEMORY_FLUSH_MIN_TURNS3
CLAPILOT_AGENT_MEMORY_FLUSH_MAX_TOKENS700
CLAPILOT_AGENT_MAINTENANCE_COMPLETION_TIMEOUT_MS240000
CLAPILOT_AGENT_MAINTENANCE_PROVIDER_TIMEOUT_MS120000
CLAPILOT_AGENT_COMPACTION_PROVIDER_TIMEOUT_MS45000
CLAPILOT_AGENT_COMPACTION_COMPLETION_TIMEOUT_MS50000
CLAPILOT_AGENT_HISTORY_COMPACTION_CHUNK_TOKENS4000
CLAPILOT_AGENT_MAINTENANCE_ALLOW_RETRIEStrue
CLAPILOT_AGENT_MAINTENANCE_ALLOW_FALLBACKStrue

Memory flush and history compaction use this shared maintenance completion policy. The provider-turn timeout is independently configurable and stays below the aggregate maintenance deadline. history_compaction additionally enforces a dedicated 50s completion hard-cap via CLAPILOT_AGENT_COMPACTION_COMPLETION_TIMEOUT_MS (default 50000). This deadline belongs to the compaction operation itself, independent of the provider adapter, so a bridge that ignores or rewrites its own request timeout still reaches the deterministic checkpoint fallback in time. The value is applied as a one-way clamp: the effective compaction completion timeout is min(configured value, 50000ms hard-cap, maintenance completion timeout), so raising it above 50s has no effect — it can only lower the compaction budget below the hard-cap, never extend it. History compaction budgets its input size, checkpoints the summary and compacted-through run index after every successful chunk, and resumes at the first unfinished chunk after a timeout. Retry and provider fallback remain bounded, but their entire execution is outside the user-visible critical path. A per-session coalescing runner prevents overlap and collapses repeated requests during one active maintenance pass into one trailing pass. The runtime /metrics response reports memory_flush and history_compaction separately, including completed/failed counts, retry/fallback counts, and total/maximum duration. Audit events additionally use distinct session.maintenance.memory_flush.* and session.maintenance.history_compaction.* event types while retaining the aggregate session.maintenance.completed event for compatibility.

Flush output is inserted directly into agent_memories (source_type = flush) with the session's standard visibility policy and subject refs, then chunked and embedded once through the normal memory path. The previous behavior — appending to /app/workspace/memory/YYYY-MM-DD.md and re-syncing/re-embedding the growing daily file on every flush — was removed, along with the read-only-filesystem fallback through /api/agent-runtime/workspace-memory. User-authored files under memory/**/*.md remain a supported input and are still synced.

The runtime stores flush state in agent_session_state.state_json.memoryFlush, including:

  • lastFlushedAt
  • lastFlushedRunCount
  • lastFilePath (now a agent_memories/<id> reference instead of a file path)
  • lastBytesWritten
  • skip reasons and last budget checks

Compactization / compaction

The code uses the term compaction.

Compaction is separate from flushing:

  • flush writes durable facts out to long-term workspace memory
  • compaction rewrites older session turns into a persisted structured summary

Compaction lives in services/clapilot-agent/src/sessions/index.mjs, but it is tightly coupled to memory behavior because background maintenance runs it after the flush check.

Compaction behavior:

  1. inspect completed agent_runs for the current session
  2. preserve a configurable number of recent turns verbatim
  3. summarize older turns with the selected model
  4. store the result in agent_session_state.state_json.compaction
  5. replay the compacted summary instead of the full older transcript on future runs

Important compaction settings:

  • enabled
  • recentTurnsPreserve
  • maxSummaryTokens
  • minTurnsSinceCompact
  • maxHistoryShare
  • qualityGuardMaxRetries
  • transcriptBytesThreshold

Current safeguard defaults in this repo:

  • flush first, then compaction
  • flush checks the last 8 completed runs and triggers from about 18,000 transcript bytes or prompt-budget pressure
  • flush waits for at least 3 new turns unless the prompt budget is already near the limit
  • compaction preserves the most recent 3 turns verbatim
  • compaction targets about 900 summary tokens
  • compaction waits for at least 2 new turns between non-budget-triggered passes
  • compaction lets raw history occupy up to about 50% of the model context before becoming aggressive
  • compaction retries the summary quality guard once before falling back
  • retrieved context is dropped before replayed recent turns when the prompt still does not fit
  • foreground runs never wait for flush or compaction; they cap and token-fit recent history against the last completed summary
  • background maintenance is single-flight per session and coalesces repeated triggers
  • embedded runtime runs skip the native Clapilot pre-flush and native Clapilot compaction path because the embedded engine already owns pre-compaction memory flush plus session auto-compaction

In practice that means:

  • plain native provider-loop runs use Clapilot's DB-backed flush + compaction state in agent_session_state.state_json
  • embedded runtime runs use the embedded engine's own flush + compaction semantics
  • the Sessions inspector now surfaces the embedded runtime as the maintenance owner and mirrors the embedded compaction count into native diagnostics, but it still cannot show the embedded Pi summary text because that summary stays inside the embedded session file

Session summaries

The runtime no longer writes a raw summary memory row into agent_memories after every run. That writer stored the full unsummarized [Input]/[Output] turn text a second time (the lossless context graph already persists it verbatim), which doubled storage and embedding cost and polluted semantic recall with raw chatter.

Existing source_type = summary rows from older versions remain searchable and remain valid Memory Dreaming input. Dreaming v2 cites them as sources but does not supersede them merely because it derived a candidate assertion.

Durable per-session knowledge now flows through three intentional channels instead:

  • shared-fact extraction (source_type = fact)
  • pre-compaction memory flush (source_type = flush)
  • the lossless context graph for verbatim history

Retention

A retention sweep keeps the memory stores from growing without bound. It runs automatically after each scheduled Memory Dreaming job and can be invoked manually via POST /internal/memory/retention.

What it does:

  • deletes superseded/archived memory rows whose archived_at is older than the configured window (their chunks cascade); dream version snapshots remain for audit
  • caps agent_knowledge_evidence rows per entity/claim/edge, keeping the most recent
  • optionally prunes old agent_context_messages that are already covered by a summary and no longer part of the live context assembly — disabled by default because it trades away the "lossless" guarantee for storage

Defaults and environment variables:

Env varDefault
CLAPILOT_AGENT_MEMORY_RETENTION_ENABLEDtrue
CLAPILOT_AGENT_MEMORY_RETENTION_SUPERSEDED_DAYS30
CLAPILOT_AGENT_MEMORY_RETENTION_EVIDENCE_MAX50
CLAPILOT_AGENT_MEMORY_RETENTION_CONTEXT_MESSAGES_ENABLEDfalse
CLAPILOT_AGENT_MEMORY_RETENTION_CONTEXT_MESSAGES_DAYS180

For Dreaming v2, rollback is governed by human review state rather than the legacy superseded-row retention window. A system-policy activation is deliberately reversible; rollback becomes blocked only after a human approves/rejects the assertion or proposal, or after a proposal is published. The superseded-purge window remains relevant to legacy dream-v1 restoration only.

Consolidation

Consolidation is the final per-partition stage of Memory Dreaming v2. It runs after assertion promotion and Knowledge Graph extraction and is bounded by the same exact (instance_key, audience kind, audience key) partition. After fresh-input partitions finish, each Dream run also selects the most recently updated eligible quiet partitions, up to the configured sweep cap, and runs the same bounded partition logic. Already-processed partitions are excluded, and stats_json.consolidation.sweep records the selected envelopes and results. It never widens an audience boundary and never uses lexical fallback pairing when an active embedding generation is unavailable.

The stage has three mutation passes:

  1. Assertion same-slot resolution. It selects currently valid approved assertions in the exact partition and same canonical subject/predicate slot, then considers only pairs whose active-generation assertion embeddings meet the similarity threshold. Pairs are sent to the maintenance model in strict-JSON batches of at most eight. The model may classify a pair as duplicate, contradiction, or unrelated, but code chooses and validates every outcome. Duplicate survivors use higher trust, then newer creation time. A contradiction is auto-superseded only when the proposed survivor is strictly newer and has equal or higher trust; a lower-trust proposed winner or older winner becomes an open conflict with both assertions left standing. Once either id has participated in a decision during the run, later pairs containing it are skipped. Supersession uses the normal assertion ledger, so a recently injected losing assertion automatically receives the existing Phase 2 corrected retrieval-feedback event and its projection lifecycle is queued through the normal outbox.
  2. Knowledge entity merging. The deterministic pass merges same-type entities in the exact instance/visibility partition when normalized labels match or aliases intersect. Normalization lowercases, trims and collapses whitespace, and removes diacritics. Code blocks cross-instance, cross-type, cross-visibility-partition, and conflicting non-null linked-record identities. The vector pass additionally requires same type and the configured entity-vector threshold, then asks the maintenance model for a conservative strict-JSON identity decision with aliases and up to five claims per entity. The survivor has more evidence rows, with older creation time as the tie-breaker. A merge unions aliases, re-points claims, edges, and evidence, resolves duplicate-row collisions by evidence/confidence, supersedes merge-created self-loops, and quarantines the loser with mergedIntoEntityId metadata. Claim and edge source_refs are never removed, replaced, or rewritten: they remain cumulative as required by the migration 209 repair. Changed survivor text is sent through the existing content-hash-aware knowledge re-embedding path after the stage.
  3. Conservative stale pruning. Active claims and edges are eligible only when their own updated_at is older than the stale window and memory_v2_graph_refs_have_current_support(source_refs) is false at mutation time. Supported rows and newer unsupported rows remain untouched. Eligible rows become superseded; the existing orphan quarantine helper then handles entities with no active supported claim or non-self edge.

Every assertion, conflict, relation, entity, claim, edge, evidence, stale-row, and orphan mutation made by consolidation is preceded in the same transaction by an agent_memory_v2_cleanup_audit record. Audit payloads contain full before-images and merge re-point mappings, including moved evidence ids, so an operator can reverse a change by hand. These operations intentionally are not part of rollbackDream: consolidation can act on rows that existed before the current Dream, while Dream rollback compensates only rows created by that Dream partition.

Dry-run mode still performs candidate queries, strict model judgments, policy checks, caps, and proposal generation, and writes the complete proposals/diagnostics to the Dream row. It does not mutate the assertion ledger, graph, evidence, relations, projections, or embeddings.

Retrieval applies non-destructive recency decay to Knowledge Graph claim and edge scores:

decay = max(decayFloor, 0.5 ^ (ageDays / decayHalfLifeDays))

Age uses the latest available evidence activity when present and otherwise the row's updated_at. The default 180-day half-life bottoms out at 0.5, so older supported knowledge remains retrievable. When enabled, returned claim/edge items may include decayFactor; disabling decay returns the prior scores and omits the field. Decay never writes to the database.

Consolidation caps and environment defaults:

Env varDefaultMeaning
CLAPILOT_AGENT_MEMORY_CONSOLIDATIONtrueEnable the post-Dream consolidation stage
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_ASSERTION_SIMILARITY_THRESHOLD0.65Minimum active-generation assertion cosine similarity
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_ENTITY_MERGE_SIMILARITY_THRESHOLD0.90Minimum entity cosine similarity before model judgment
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_MAX_CANDIDATE_PAIRS_PER_PARTITION40Maximum assertion candidate pairs considered per partition
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_MAX_ASSERTION_RESOLUTIONS_PER_RUN25Maximum assertion supersessions/conflicts proposed per partition run
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_MAX_ENTITY_MERGES_PER_RUN10Maximum entity merges proposed per partition run; deterministic matches consume budget first
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_MAX_SWEEP_PARTITIONS5Maximum additional active-generation assertion partitions consolidated after fresh Dream partitions
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_STALE_DAYS120Minimum row age for unsupported claim/edge pruning
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_DECAY_ENABLEDtrueApply retrieval-only graph recency decay
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_DECAY_HALF_LIFE_DAYS180Graph score half-life in days
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_DECAY_FLOOR0.5Minimum graph decay factor
CLAPILOT_AGENT_MEMORY_CONSOLIDATION_DRY_RUNfalseProduce full proposals and diagnostics without mutations

Admin diagnostics

The ClapilotAICore Memory page pulls status from the native runtime, including:

  • prompt file hashes
  • workspace memory sync status
  • embedding strategy and fallback state
  • canonical assertion/evidence/conflict counts, embedding-generation backlog, retrieval feedback, and ingestion-outbox health
  • an active-assertion card that explains automatic safe activation and links review-required exceptions to the Learning settings page
  • counts by memory scope and source type
  • counts by visibility_scope
  • lossless context graph counts
  • last flush timestamp
  • imported legacy transcript counts

The same page now also exposes the maintenance surfaces that previously required direct endpoint calls:

  • recent Memory Dreaming runs with status, model, input/output counts, automatic graph extraction counts, rollback state, a per-dream graph filter action, and a separate diagnostic graph rebuild/backfill action
  • a persisted Dreaming-model selector with a separate compact save action; an empty selection follows the runtime resolution fallback above, while Run Dreaming still submits the currently selected model explicitly for that manual run
  • a rollback action for one dream run
  • a Knowledge Graph inspector with explicit Personal, Team, Channel, and combined admin views that explains the automatic approved-assertion projection, opens the current audience-scoped or dream-filtered graph search result in a scrollable, zoomable interactive node/edge dialog, and expands a selected node in place into a source-aware detail card whose header stays fixed while the complete body—including explainer, metadata, assertions, and relationships—scrolls as one region; the inspector also lists searchable entities, claims, edges, visibility scopes, linked records, subject refs, source refs, confidence, and evidence counts

The graph dialog lays out nodes with a deterministic force simulation: connected entities cluster together, disconnected clusters are packed side by side, and parallel relations between the same pair of nodes are drawn as separated curves. Clicking a node selects it and smoothly centers it at a moderate zoom (it never force-zooms past the current level by more than a gentle step), clicking the background clears the selection, and panning/scroll-zoom track the cursor precisely. Node captions are display-cleaned summaries: fallback entity labels such as Session: agent:main:<uuid> are shortened to their readable segments, UUID-style user/document references are abbreviated, and literal fact nodes strip list-marker/Shared fact boilerplate so the caption shows the stored statement itself. A legend above the stage explains entity vs. stored-fact nodes and relation vs. fact links. The complete assertion and relationship text stays inside the expanded node card; long node histories scroll within that card without moving or shrinking the graph stage.

The admin graph inspector intentionally shows all graph scopes for diagnostics. Agent bootstrap and agent-facing graph tools still use the normal runtime visibility rules, so this does not widen model-visible memory.

If the selected model returns malformed Dreaming JSON, the runtime records the failure shape and marks the dream run as skipped without rewriting any memory rows. Re-run dreaming (optionally with a different model) once the model issue is resolved; the invariant that every promoted dream memory cites source memory ids is unchanged.

Relevant docs: