-
Notifications
You must be signed in to change notification settings - Fork 4
Domains Memory
The memory domain manages two distinct stores with a promotion bridge between them. The task memory bank (TaskMemoryBank in src/domains/memory/task-bank.ts) is per-session, in-memory execution memory that the background intervention middleware reads and writes on every step. It holds three classes: a private status entry tracking the policy's progress model, knowledge entries for facts, and procedural entries for procedures and failure lessons. The bank is capped (20 knowledge, 30 procedural by default) and evicts oldest entries when a cap is exceeded.
The durable memory store (src/domains/memory/store.ts, src/domains/memory/operations.ts) persists approved, evidence-linked memory records to <dataDir>/memory/records.json. These records carry scopes (global, repo, runtime, agent, and others), applicability conditions, confidence scores, and provenance. Retrieval filters by approval status, scope, and active identity before token-budget and item-count selection.
The promotion bridge (src/domains/memory/promotion.ts, src/domains/memory/proposal.ts, src/domains/memory/task-bank-promotion.ts) converts task-bank entries or evidence findings into durable records that require human approval before they enter any prompt.
| File | Key symbols | Role |
|---|---|---|
src/domains/memory/index.ts |
re-exports all public API | Domain entry point |
src/domains/memory/task-bank.ts |
TaskMemoryBank, TaskMemoryEntry, TaskMemorySnapshot
|
Session-scoped in-memory bank |
src/domains/memory/task-memory-policy.ts |
runTaskMemoryPolicy, TaskMemoryModelClient, TaskMemoryPolicyDecision
|
Background intervention engine |
src/domains/memory/store.ts |
loadMemoryRecords, writeMemoryRecords, upsertMemoryRecord, readMemoryStoreSnapshot
|
Durable store I/O |
src/domains/memory/operations.ts |
approveMemoryRecord, rejectMemoryRecord, eligibleMemoryRecords, canonicalMemoryRepositoryIdentity
|
Record lifecycle and eligibility |
src/domains/memory/promotion.ts |
proposeMemoryPromotion, memoryRecordFromPromotion, validateMemoryScopeSelection
|
Task-bank-to-store bridge |
src/domains/memory/proposal.ts |
proposeMemoryFromEvidence, memoryRecordFromEvidence
|
Evidence-to-store bridge |
src/domains/memory/task-memory-handoff.ts |
taskMemoryHandoffSnapshot, seedTaskMemoryBank, parseTaskMemoryHandoffSnapshot
|
Cross-session handoff |
src/domains/memory/validate.ts |
validateMemoryRecord, validateMemoryStore
|
Schema validation |
src/domains/memory/types.ts |
MemoryRecord, MemoryScope, MemoryRetrievalOptions
|
Core types |
src/domains/memory/prompt-section.ts |
selectMemoryForPrompt, renderMemoryPromptSection, buildMemoryPromptSection
|
Prompt injection selection |
src/domains/memory/relevance.ts |
rankMemoryByRelevance, rankMemoryByPrecomputedScore
|
Relevance ranking |
src/domains/memory/task-memory-telemetry.ts |
createTaskMemoryTelemetrySink, taskMemoryBankDelta
|
Step telemetry |
src/domains/memory/task-bank-promotion.ts |
proposeInjectedTaskMemory |
Auto-promotion on injection |
src/domains/memory/task-memory-spend.ts |
foldTaskMemorySpend, readTaskMemorySpendSummary
|
Lifetime cost aggregation |
src/domains/memory/task-memory-status.ts |
TaskMemoryOperatorStatus, describeTaskMemoryActivity
|
Operator-facing projections |
The primary runtime flow is the background memory intervention, driven by runTaskMemoryPolicy in src/domains/memory/task-memory-policy.ts.
-
Trigger: The middleware calls
runTaskMemoryPolicy(bank, client, input)whereinput.taskis the current task description,input.trajectoryis the recent tool-call history, andinput.deterministicTriggerindicates whether the step is mandatory (e.g., post-compaction) or opportunistic. -
Prompt construction:
buildMemoryInterventionUserPrompt(fromsrc/domains/prompts/memory-intervention.ts) receives the task (truncated to 2000 chars), the bank's rendered content viabank.render(input.maxTokens), and the trajectory as JSON (truncated to 4000 chars byrenderTrajectory). -
Model call: The
TaskMemoryModelClient.complete()method is called with a system prompt, user prompt, token budget, and abort signal. The call races against a timeout (default 60s, pinned byTASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS) and an external cancellation signal. -
Response parsing:
readPolicyStepparses the model's XML-like envelope. It locates<operations>...</operations>by tracking JSON bracket depth (not tag position), parses the operation array, and reads the optional<context_for_action>reminder. Structural violations reject the batch; unrecognizedopverbs are dropped individually. -
Operation application:
resolveOperationsreconciles the model's operation list against the bank's current state, repairing invented entry IDs.applyOperationsthen callsbank.updateStatus,bank.saveKnowledge,bank.saveProcedural, orbank.deleteEntryfor each resolved operation. -
Reminder gating: If a reminder was produced, it is checked for over-budget length, duplication, citation validity (spontaneous reminders must cite bank entries), resolved-failure detection, and workspace path existence. Passing reminders are recorded via
bank.recordInjection(citedIds). -
Telemetry: The result is settled through
settle(), which reports toinput.onEnvelope(when tracing is enabled) and returns aTaskMemoryPolicyResultwith decision, reason, token counts, and the usage row.
When the context domain builds a prompt, it calls buildMemoryPromptSection or selectMemoryForPrompt from src/domains/memory/prompt-section.ts:
-
eligibleMemoryRecords(insrc/domains/memory/operations.ts) filters records by:approved === true, non-emptyevidenceRefs, noregressions, scope membership, and identity match (repository, runtime, agent). Repository matching requiresrecord.repository.key === activeRepository.keywhere both are canonical absolute paths. -
Relevance ranking (optional):
rankMemoryByRelevancescores candidates by task-term overlap (weight 1), path overlap (weight 8), and symbol overlap (weight 4). One zero-score legacy-priority record gets a fallback slot at position 1.rankMemoryByPrecomputedScoreapplies an external async-resolved ranking on top. -
Budget enforcement:
ceilChars(section.length)is compared againsttokenBudget(default 400). Selection stops when the next record would exceed the budget or the item limit (default 5).
Session isolation: The task bank is cleared when the session changes. The middleware's getSettings callback checks sessionId !== bankSessionId and calls bank.clear(), which resets status, knowledge, procedural, and the ID counter. Late-arriving model completions from the old session are blocked by isCurrent() checks in runTaskMemoryPolicy, which return scope_changed without applying any operations.
Status privacy: TaskMemoryBank.render() never includes the status entry, because a mid-turn reminder must not expose the policy's private progress model. The only exception is renderRestoredState(), which is called after compaction destroyed the agent's working state and hands status back with priority.
Approval gate: Durable records start with approved: false. eligibleMemoryRecords filters on record.approved, so unapproved records never enter any prompt. Approval is a separate operator action via approveMemoryRecord.
Repository identity: canonicalMemoryRepositoryIdentity resolves symlinks and returns null for missing or non-canonical paths. A repo-scoped record without a repository field never enters any repository prompt. A repo-scoped record with a mismatched repository key is excluded.
Content redaction: Both handoff rendering (renderTaskMemoryHandoffSnapshot) and promotion (memoryRecordFromPromotion) call redactSecretsText from src/domains/evidence/redact.ts. Existing [redacted:*] markers are preserved by masking them with private-use Unicode tokens before redaction, then restoring them afterward.
New memory scopes: Add the scope to MEMORY_SCOPES in src/domains/memory/types.ts, add its rank to scopeRank in src/domains/memory/store.ts, and handle it in scopeApplicability and applyScopeIdentity in src/domains/memory/promotion.ts. The prompt-section defaults (MEMORY_PROMPT_DEFAULT_SCOPES) control which scopes are eligible for chat-loop injection.
New operation verbs: The policy's readOperations function in src/domains/memory/task-memory-policy.ts recognizes update_status, save_knowledge, save_procedural, and delete. Adding a new verb requires adding a case there, a matching method on TaskMemoryBank, and a corresponding branch in applyOperations.
New relevance signals: rankMemoryByRelevance in src/domains/memory/relevance.ts scores by term, path, and symbol overlap. New signals require adding a weight to MEMORY_RELEVANCE_WEIGHTS and a matching feature extraction in the candidate mapping.
New trigger types: TaskMemoryTelemetryTrigger in src/domains/memory/task-memory-telemetry.ts lists the valid trigger reasons. Adding one requires updating the TRIGGERS set and the telemetry record parser.
tests/extended/memory-scope.test.ts demonstrates:
- Repository-scoped records are excluded when
activeRepositoryisnullor a different repository, while global records always pass. - Promotion from a task-bank entry produces an unapproved record with
approved: false, redacted content ([redacted:assignment]replaces the secret), and provenance linking back to the source session and entry. - Handoff snapshots exclude private status entries, redact secrets, use the
clio-coder-task-memoryfence language (with legacyclio-task-memoryalso parseable), and seed into a target bank with deduplication (second seed reportsseeded: 0, skipped: 2).
tests/extended/memory-session-isolation.test.ts demonstrates:
- A memory completion from session A that arrives after the session has switched to B does not populate session B's bank, does not deliver a reminder, and does not propose memory under session B's identity.
- Session transitions (new, resume, roundtrip, fork, branch) abort the in-flight model call, clear the bank, and prevent late-arriving completions from writing.
-
isCurrent()returningfalseblocks bank writes even when the model call completed successfully. - Operator cancellation via
AbortSignalrevokes a pending step without erasing already-completed knowledge. - Provider usage arriving after the policy deadline is recorded exactly once through
onStepUsage.
flowchart TB
subgraph SessionScoped["Session-scoped (in-memory)"]
TB[TaskMemoryBank] -->|render| Policy[runTaskMemoryPolicy]
Policy -->|saveKnowledge/saveProcedural/updateStatus| TB
Policy -->|recordInjection| TB
TB -->|snapshot| Handoff[taskMemoryHandoffSnapshot]
end
subgraph Durable["Durable store (records.json)"]
Store[(records.json)] -->|loadMemoryRecords| Ops[operations.ts]
Ops -->|eligibleMemoryRecords| Select[selectMemoryForPrompt]
Select -->|renderMemoryPromptSection| Prompt[Context domain]
end
subgraph Bridges["Promotion bridges"]
TB -->|proposeInjectedTaskMemory| Promo[proposeMemoryPromotion]
Evidence[Evidence domain] -->|proposeMemoryFromEvidence| Promo
Promo -->|upsertMemoryRecord| Store
Handoff -->|seedTaskMemoryBank| TB
Handoff -->|parseTaskMemoryHandoffSnapshot| CLI[CLI memory promote]
CLI -->|proposeMemoryPromotion| Store
end
subgraph Telemetry["Telemetry (steps.jsonl)"]
Policy -->|settle| Telem[createTaskMemoryTelemetrySink]
Telem -->|appendFileSync| Ledger[(steps.jsonl)]
Ledger -->|foldTaskMemorySpend| Spend[TaskMemorySpendSummary]
end
-
TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MSmust equal the settings default insrc/core/defaults.ts. A contract test pins the pair. Changing one without the other means any path that misses the settings object silently gets a different timeout. -
readPolicyStepuses bracket-depth counting, not tag matching, to find the end of the operations array. A session working on the memory tier can write the envelope's own grammar into operation content, which breaksindexOf/lastIndexOftag searches. Do not replace the depth counter with a simpler search. -
renderTrajectorydrops whole steps to fit the budget rather than truncating individual fields, except when a single step exceeds the limit. The alternative (string slicing) corrupted JSON mid-token. -
The task bank's
#evictOldestsorts bylastTouchedAtthencreatedAtthen ID.render()sorts bylastTouchedAtdescending (newest first) and filters bykinds. These are different orderings for different purposes. -
validateMemoryRecordenforces scope-identity coupling: repo scope requires a repository identity, runtime scope requires a runtime identity, agent scope requires an agent identity. A mismatch produces a validation error. -
canonicalMemoryRepositoryIdentityreturnsnullfor missing paths. A repo-scoped record whose repository no longer exists is excluded from retrieval, which is intentional fail-closed behavior. -
The handoff snapshot parser (
parseTaskMemoryHandoffSnapshot) rejects version 2 entries that lack timestamps. Legacy version 1 entries are accepted without them. The CLI'smemory promotecommand requires version 2 snapshots and throws on version 1. -
foldTaskMemorySpendcountsendpoint_busyskips separately from llm steps. ThehitRatedenominator isllmSteps, not total steps, so a machine with a permanently busy endpoint shows 0% hit rate rather than a division-by-zero or inflated rate. -
taskMemoryTelemetryRecordusesnonNegativeInteger(coercing invalid values to 0) whileparseTaskMemoryTelemetryRecordusesisNonNegativeInteger(rejecting invalid values). The writer is lenient because telemetry is observational; the parser is strict because it validates external data. -
The
exactOptionalPropertyTypesconvention applies throughout: optional fields are passed with...(x !== undefined ? { x } : {}), neverx: undefined. This is visible invalidate.tswhere optional fields are conditionally spread.
Source and generation metadata
title: "Domains memory"
summary: "Session-scoped task memory bank for background intervention, durable approved memory store for cross-session retrieval, and the promotion bridge between them."
sources:
- "src/domains/memory/index.ts"
- "src/domains/memory/task-bank.ts"
- "src/domains/memory/task-memory-policy.ts"
- "src/domains/memory/store.ts"
- "src/domains/memory/operations.ts"
- "src/domains/memory/promotion.ts"
- "src/domains/memory/proposal.ts"
- "src/domains/memory/task-memory-handoff.ts"
- "src/domains/memory/validate.ts"
- "src/domains/memory/types.ts"
- "src/domains/memory/prompt-section.ts"
- "src/domains/memory/relevance.ts"
- "src/domains/memory/task-memory-telemetry.ts"
- "src/domains/memory/task-bank-promotion.ts"
- "src/domains/memory/task-memory-spend.ts"
- "src/domains/memory/task-memory-status.ts"
symbols:
- "TaskMemoryBank"
- "runTaskMemoryPolicy"
- "proposeMemoryPromotion"
- "proposeMemoryFromEvidence"
- "selectMemoryForPrompt"
- "validateMemoryRecord"
- "taskMemoryHandoffSnapshot"
- "seedTaskMemoryBank"
tests:
- "tests/extended/memory-scope.test.ts"
- "tests/extended/memory-session-isolation.test.ts"
invariants:
- "Task bank entries are session-scoped and in-memory; only promoted entries enter the durable store"
- "Durable memory records require human approval before retrieval into prompts"
- "Repository-scoped records match only on exact canonical repository path identity"
- "Status entries are private: never rendered to the action agent, never exported to handoff snapshots"
validate:
- "pnpm run test:file -- tests/extended/memory-scope.test.ts"
- "pnpm run test:file -- tests/extended/memory-session-isolation.test.ts"Clio Coder · Repository · Website · Documentation
Wiki v0.1 · Developing implementation reference · Source snapshot: 657dce13d. Authored architecture documents define the product contracts.
- Clio Coder GUI Client
- apps / clio-coder-gui
- Apps clio coder gui server
- Apps clio coder gui tests
- apps
- Architecture
- Command-line surfaces
- Core
- Domains agents
- Config Domain
- Context Domain
- Dispatch domain
- Domains evidence
- Domains extensions
- Domains gateway
- domains
- Domains interop
- Domains lifecycle
- Domains memory
- Middleware Domain
- Domains mux
- Domains observability
- Domains plugins
- Prompt Compiler
- Domains providers
- Domains quota
- Domains resources
- Domains safety
- Domains scheduling
- Domains session
- Vendored Tool Registry and Resolution
- Engine
- Engine acp
- Engine apis
- engine
- Entry point
- Interactive
- interactive
- Interactive overlays
- Interactive renderers
- clio-coder wiki
- Scripts
- Contract tests
- Tests extended
- tests
- Tools
- Tools data
- tools
- Tools verify
- Worker runtime